<!DOCTYPE html>
<html lang="cn" dir="auto">

<head><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="index, follow">
<title>ThreadLocal 分析（下）——使用和源码 | 染竹君的个人博客</title>
<meta name="keywords" content="Java, ThreadLocal, Reference">
<meta name="description" content="ThreadLocal 的使用、注意事项及源码分析。">
<meta name="author" content="SadBird">
<link rel="canonical" href="https://www.liyangjie.cn/posts/work/threadlocal-source/">
<link crossorigin="anonymous" href="/assets/css/stylesheet.min.4b7d810bc0c98044b1c7a72962d55d7f125a07c3ed5cf31e670c3fdecc98b341.css" integrity="sha256-S32BC8DJgESxx6cpYtVdfxJaB8PtXPMeZww/3syYs0E=" rel="preload stylesheet" as="style">
<link rel="icon" href="https://www.liyangjie.cn/favicon/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="https://www.liyangjie.cn/favicon/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://www.liyangjie.cn/favicon/favicon-32x32.png">
<link rel="apple-touch-icon" href="https://www.liyangjie.cn/favicon/apple-touch-icon.png">
<link rel="mask-icon" href="https://www.liyangjie.cn/favicon/safari-pinned-tab.svg">
<meta name="theme-color" content="#2e2e33">
<meta name="msapplication-TileColor" content="#2e2e33">
<noscript>
    <style>
        #theme-toggle,
        .top-link {
            display: none;
        }

    </style>
    <style>
        @media (prefers-color-scheme: dark) {
            :root {
                --theme: rgb(29, 30, 32);
                --entry: rgb(46, 46, 51);
                --primary: rgb(218, 218, 219);
                --secondary: rgb(155, 156, 157);
                --tertiary: rgb(65, 66, 68);
                --content: rgb(196, 196, 197);
                --hljs-bg: rgb(46, 46, 51);
                --code-bg: rgb(55, 56, 62);
                --border: rgb(51, 51, 51);
            }

            .list {
                background: var(--theme);
            }

            .list:not(.dark)::-webkit-scrollbar-track {
                background: 0 0;
            }

            .list:not(.dark)::-webkit-scrollbar-thumb {
                border-color: var(--theme);
            }
        }

    </style>
</noscript>
<meta name="baidu-site-verification" content="code-9oLyeix0aK" />
<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?4a41bf85d719f0e8c3165fc76904f546";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>


<script defer crossorigin="anonymous" src="/js/katex.min.f1c5d0fdee4ec5ea6aa1428f89bd54cdc1ce603e04393519f56720fe7cc9e0f5.js" integrity="sha256-8cXQ/e5OxepqoUKPib1UzcHOYD4EOTUZ9Wcg/nzJ4PU="></script>


<script defer crossorigin="anonymous" src="/js/auto-render.min.0a59db5a3ebdb7706f0dd73f90a2bc9a7370a930b043fb388c8005f5f721242d.js" integrity="sha256-ClnbWj69t3BvDdc/kKK8mnNwqTCwQ/s4jIAF9fchJC0="></script>

<script>
    
    document.addEventListener("DOMContentLoaded", function() {
        renderMathInElement(document.body, {
          
          
          delimiters: [
              {left: '$$', right: '$$', display: true},
              {left: '$', right: '$', display: false},
              {left: '\\(', right: '\\)', display: false},
              {left: '\\[', right: '\\]', display: true}
          ],
          
          throwOnError : false
        });
    });

    
    window.WebFontConfig = {
        custom: {
            families: ['KaTeX_AMS', 'KaTeX_Caligraphic:n4,n7', 'KaTeX_Fraktur:n4,n7',
            'KaTeX_Main:n4,n7,i4,i7', 'KaTeX_Math:i4,i7', 'KaTeX_Script',
            'KaTeX_SansSerif:n4,n7,i4', 'KaTeX_Size1', 'KaTeX_Size2', 'KaTeX_Size3',
            'KaTeX_Size4', 'KaTeX_Typewriter'],
        },
    };
</script>


<script defer crossorigin="anonymous" src="/js/webfontloader.min.min.73a1e4ae3f25b4544fa75582fb432fa231158ddf819fbc1f06698845f99c66f2.js" integrity="sha256-c6Hkrj8ltFRPp1WC&#43;0MvojEVjd&#43;Bn7wfBmmIRfmcZvI="></script>


<script async src="https://www.googletagmanager.com/gtag/js?id=G-C6GDZ56F4S"></script>
<script>
var doNotTrack = false;
if (!doNotTrack) {
	window.dataLayer = window.dataLayer || [];
	function gtag(){dataLayer.push(arguments);}
	gtag('js', new Date());
	gtag('config', 'G-C6GDZ56F4S', { 'anonymize_ip': false });
}
</script>
<meta property="og:title" content="ThreadLocal 分析（下）——使用和源码" />
<meta property="og:description" content="ThreadLocal 的使用、注意事项及源码分析。" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://www.liyangjie.cn/posts/work/threadlocal-source/" />
<meta property="og:image" content="https://i.loli.net/2021/09/25/HTBpP15DImOXl7A.jpg" /><meta property="article:section" content="posts" />
<meta property="article:published_time" content="2021-09-19T02:44:58&#43;08:00" />
<meta property="article:modified_time" content="2021-09-19T02:44:58&#43;08:00" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://i.loli.net/2021/09/25/HTBpP15DImOXl7A.jpg" />
<meta name="twitter:title" content="ThreadLocal 分析（下）——使用和源码"/>
<meta name="twitter:description" content="ThreadLocal 的使用、注意事项及源码分析。"/>


<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position":  1 ,
      "name": "Posts",
      "item": "https://www.liyangjie.cn/posts/"
    }, 
    {
      "@type": "ListItem",
      "position":  2 ,
      "name": "Work",
      "item": "https://www.liyangjie.cn/posts/work/"
    }, 
    {
      "@type": "ListItem",
      "position":  3 ,
      "name": "ThreadLocal 分析（下）——使用和源码",
      "item": "https://www.liyangjie.cn/posts/work/threadlocal-source/"
    }
  ]
}
</script>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "ThreadLocal 分析（下）——使用和源码",
  "name": "ThreadLocal 分析（下）——使用和源码",
  "description": "ThreadLocal 的使用、注意事项及源码分析。",
  "keywords": [
    "Java", "ThreadLocal", "Reference"
  ],
  "articleBody": "ThreadLocal 基本使用 下面示例模拟两个请求，在两个线程完成任务，任务由两部分组成，其中 Service1 负责生成 TRACE_ID 和一部分任务并调用 Service2 ，而 Service2 可以使用 TRACE_ID ，完成剩余部分任务，最后清理 TRACE_ID 。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73  public class ThreadLocalDemo { public static void main(String[] args) { Service2 service2 = new Service2(); Service1 service1 = new Service1(service2); // 两个线程模拟两个请求  Thread request1 = new Thread(service1::service1); Thread request2 = new Thread(service1::service1); request1.start(); request2.start(); } } /** * 全局上下文，管理TRACE_ID */ class TraceContext { // API文档中推荐ThreadLocal声明为private static  private static final ThreadLocalString CONTEXT = new ThreadLocal(); public static void set(String traceId) { CONTEXT.set(traceId); } public static String get() { return CONTEXT.get(); } public static void remove() { CONTEXT.remove(); } } /** * 负责创建TRACE_ID，调用Service2 */ class Service1 { private static final String TRACE_ID_PREFIX = \"X-TRACE-ID-\"; private static final AtomicInteger ID = new AtomicInteger(1); private Service2 service2; public Service1(Service2 service2) { this.service2 = service2; } public void service1() { // 在service1中先设置TRACE_ID  TraceContext.set(TRACE_ID_PREFIX + ID.getAndIncrement()); // 打印新生成的TRACE_ID，模拟一些业务操作  System.out.println(\"generate new TRACE_ID: \" + TraceContext.get() + \" - do something in service1, current thread is: \" + Thread.currentThread()); // 再调用service2  service2.service2(); } } /** * 使用TRACE_ID，并完成清理工作 */ class Service2 { public void service2() { // 打印TRACE_ID， 模拟对TRACE_ID的操作  System.out.println(\"current TRACE_ID is: \" + TraceContext.get() + \" - do something in service2, current thread is: \" + Thread.currentThread()); // 操作完成后，手动清理  TraceContext.remove(); } }   以上就是 ThreadLocal 最基本的使用场景，通过 ThreadLocal 来透传全局的某些上下文信息，以便后续的分析和追踪（logback 中实现 MDC 正是使用了 ThreadLocal）。\n ThreadLocal 源码分析 ThreadLocal 概览 在解释具体代码之前，首先要搞清楚 ThreadLocal、Thread 及 ThreadLocal 中实际保存的 value 的关系，下面是 ThreadLocal 源码中的一段注释：\n1 2 3 4 5 6 7 8 9 10 11  /** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */ private final int threadLocalHashCode = nextHashCode();   这里面有几个关键信息：\n ThreadLocal 机制依靠的是 Thread.threadLocals 和 Thread.inheritableThreadLocals 这两个哈希表。 每个线程有自己的 Thread.threadLocals 和 Thread.inheritableThreadLocals，线程间通过这种方式避免了共享，实现了隔离。 ThreadLocal 自身的作用是作为 Thread.threadLocals 和 Thread.inheritableThreadLocals 的 Key，因此每个 ThreadLocal 都需要有自己的hashcode，即 threadLocalHashCode。 Thread.threadLocals 和 Thread.inheritableThreadLocals 处理冲突的方式为 linear-probe，即线性探测。  在此基础上，我们先去 Thread 中找到 Thread.threadLocals 和 Thread.inheritableThreadLocals：\n1 2 3 4 5 6 7 8 9 10 11 12 13  // ...  /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // ...   现在，可以画出一个大致的关系草图如下，先以 threadLocals 为例，inheritableThreadLocals 原理与 threadLocals 相同：\n\r\r从图上能更直观地看出 ThreadLocal 的「地位」，在层次结构上，它只是作为 Thread 中一个哈希表的 Key。但它的功能可不仅仅是个 Key，再回头看看 threadLocals 源码注释：\n1 2 3  /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;   不难看出 ThreadLocal 还肩负着维护 threadLocals 的重要使命，即对 threadLocals 进行增删改查等操作。 下面就对 ThreadLocal 的这两大作用分别进行源码分析。\n作为哈希表的 Key 上面已经提到，每个 ThreadLocal 都有 threadLocalHashCode 属性，这个值将作为 Key 的 hashcode 参与到后续的计算。threadLocalHashCode 的计算方式如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }   可以看到，第1个 ThreadLocal 的 threadLocalHashCode 为 0，此后，每新建一个 ThreadLocal 对象，该对象的 threadLocalHashCode 值就为上一个对象的 threadLocalHashCode 值加上 HASH_INCREMENT。\n说得直白点，设 HASH_INCREMENT 值为 $a$，那么第 1 个 ThreadLocal 对象的 threadLocalHashCode 为 $0 * a$，第 2 个为 $1 * a$，第 3 个为 $2 * a$，… ，第 n 个为 $(n - 1) * a$，属于乘法 hash。\n代码中，这个 $a$ 值设定为一个特殊的数字：0x61c88647，理由在注释中已经给出，这个值能够使 Key 值在大小为 $2 ^ n$ 的哈希表上均匀地分布，至于其中的原理就不继续深究，和黄金分割、斐波那契相关，感兴趣的可以自行查阅资料。\n继续查看 ThreadLocal 的静态内部类 ThreadLocalMap，它在构造函数中将 Key 的 hashcode 映射到具体位置的代码如下：\n1 2 3 4 5  // ... private static final int INITIAL_CAPACITY = 16; //... int i = key.threadLocalHashCode \u0026 (INITIAL_CAPACITY - 1);   为了证明这种方式的有效性，下面进行一个小的模拟实验：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  private static final int A = 0x61c88647; public static void main(String[] args) { hashSequence(1); hashSequence(2); hashSequence(3); hashSequence(4); } private static void hashSequence(int n) { int size = 1  n; int mod = size - 1; System.out.print(\"hash seq for \" + size + \" size table: \"); for (int i = 0; i  size; i++) { int index = (i * A) \u0026 mod; System.out.print( index + \" \"); } System.out.println(); }   结果如下：\n1 2 3 4  hash seq for 2 size table: 0 1 hash seq for 4 size table: 0 3 2 1 hash seq for 8 size table: 0 7 6 5 4 3 2 1 hash seq for 16 size table: 0 7 14 5 12 3 10 1 8 15 6 13 4 11 2 9   效果拔群！\n管理 threadLocals   增、改：set 方法源码如下，关于 ThreadLocalMap 放在后面探讨，这里先简单理解为一个普通的哈希表：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  public void set(T value) { // 获取当前线程  Thread t = Thread.currentThread(); // 获取线程中的threadLocals  ThreadLocalMap map = getMap(t); if (map != null) // 不为空直接set，set方法其实是有“副作用”的，但这里暂时理解为简单的取值  // 这里this就是当前的ThreadLocal对象，作为Key  map.set(this, value); else // 为空就new Map  createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }     查：get 方法源码如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37  public T get() { // 这两行和set一模一样  Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { // map不为空，直接从map中取值，getEntry其实是有「副作用」的，但这里暂时理解为简单的取值  ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings(\"unchecked\") T result = (T)e.value; return result; } } // map为空，则需要初始化map  return setInitialValue(); } // 这个方法和set基本一模一样 private T setInitialValue() { // 获取初始值  T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 将初始值设置到map中  if (map != null) // set方法其实是有「副作用」的，但这里暂时理解为简单的取值  // 这里this就是当前的ThreadLocal对象，作为Key  map.set(this, value); else createMap(t, value); return value; } // 待子类重写，返回初始value protected T initialValue() { return null; }     删：remove 方法源码如下：\n1 2 3 4 5 6 7 8  public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) // 这里this就是当前的ThreadLocal对象，作为Key传入，最终从map中删除Key为当前ThreadLocal的元素  // 这里是线性探测法的remove，需要特别注意  // 与get、set类似，这里的remove也会有特殊的操作，这里暂时理解为简单的删除  m.remove(this); }     Java8 新增静态方法 withInitial ：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  public static S ThreadLocalS withInitial(Supplier extends S supplier) { return new SuppliedThreadLocal(supplier); } static final class SuppliedThreadLocalT extends ThreadLocalT { private final Supplier extends T supplier; SuppliedThreadLocal(Supplier extends T supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); } }   也比较容易理解，原来的写法是：\n1 2 3 4 5 6  ThreadLocalObject tl = new ThreadLocal() { @Override protected Object initialValue() { return new Object(); } }   现在可以写成这样，比较省事：\n1  ThreadLocalObject tl = ThreadLocal.withInitial(Object::new);     OK，至此，ThreadLocal 表面上的东西已经介绍得差不多了，代码都比较简单，结合上面那个草图理解起来应该没什么问题。然而，ThreadLocal 最为复杂的部分其实是它的内部类 ThreadLocalMap，下面的内容就是把这块硬骨头一点一点啃下来。\n ThreadLocalMap 源码分析 在进入源码前，需要有一些知识铺垫：\n  首先需要了解过哈希表是什么，对哈希冲突、开放地址、线性探测等概念比较熟悉，最好自己动手实现过。可以上网找找，资料挺多的，这里推荐一个入门视频：\n\r   对 Java 的弱引用有所了解，不知道的可以看看之前的这篇文章 ThreadLocal 分析（上）——Java 中的引用。\n  ThreadMap 字段 初步先看看 ThreadLocalMap 的字段：\n\r\r阅读过 HashMap 源码的话其实这些字段都不需要再解释了，非常简单，从上到下依次为：初始容量（最大桶数量）、实际的哈希表（Entry 数组，它的长度一定为 $2 ^ n$）、当前哈希表中元素的数量、下次扩容的阈值。\n其他字段都好说，这里引入关键问题的字段就是 Entry：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30  /** *... * To help deal with very large and long-lived usages, the hash table entries use * WeakReferences for keys. *... */ static class ThreadLocalMap { // ...  /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as \"stale entries\" in the code that follows. */ static class Entry extends WeakReferenceThreadLocal { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } // ... }   有没有似曾相识的感觉，在上一篇 ThreadLocal 分析（上）——Java 中的引用 中，介绍过一个 WeakHashMap，它的 Entry 定义 private static class Entry extends WeakReference ... 与这里的 Entry 如出一辙，第一段注释也写得很清楚，使用 WeakReference 作为 Key 是为了回收生命周期较长的大对象。\n留意第二段注释中有个特别的说明：「当某个 entry 满足 entry.get() == null 时（隐含条件是 entry != null），表明这个 entry 的 Key 已经不再被引用关联到，因此这个 entry 可以被的删除（expunged），这样的 entry 在代码中被称为 stale entry。」多看几遍这几个重要的单词，expunged、stale entries，后面会频繁出现。\n现在可以将第一个草图进行修改了，哈希表 ThreaedLoclaMap 中 Entry 的Key实际上是一个 WeakReference 对象，这个对象中的 referent 弱指向了实际的 ThreadLocal 对象，虚线表示弱引用：\n\r\r接下来看看 ThreadLocalMap 的构造函数（在 ThreadLocal 的 createMap 方法中使用到，忘记的话可以退回到上一节的 set 方法中查看）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  // ...  private static final int INITIAL_CAPACITY = 16; // ...  ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { // 创建初始长度为16的Entry数组  table = new Entry[INITIAL_CAPACITY]; // 将传入的 ThreadLocal作为key， Object作为value，新建第一个Entry放入哈希表中  int i = firstKey.threadLocalHashCode \u0026 (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); // 当前元素个数为1  size = 1; // 设置扩容阈值  setThreshold(INITIAL_CAPACITY); } // 设置扩容阈值为 len 的 2/3 private void setThreshold(int len) { threshold = len * 2 / 3; }   set 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92  // 计算哈希表当前位置i的下一位置，一般情况为i + 1 // 但当超过数组长度len时，就重新回到数组开头位置0 private static int nextIndex(int i, int len) { return ((i + 1  len) ? i + 1 : 0); } // 计算哈希表当前位置i的上一位置，一般情况为i - 1 // 但到达0位置时，它的上一位置是len - 1 private static int prevIndex(int i, int len) { return ((i - 1 = 0) ? i - 1 : len - 1); } private void set(ThreadLocal key, Object value) { Entry[] tab = table; int len = tab.length; // 计算参数key在哈希表中的对应的实际位置i  int i = key.threadLocalHashCode \u0026 (len-1); // 线性探测法  // 从i开始，往「后」遍历，直到i位置的Entry为null  for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { // e.get()获取当前i位置的Key，是WeakReference中的方法，有可能返回null  ThreadLocal k = e.get(); // 找到当前key，表示是修改操作，直接修改value并返回  if (k == key) { e.value = value; return; } // k为null的情况，表示该位置Entry的Key已经被回收，需要进行特殊处理，后面介绍  if (k == null) { replaceStaleEntry(key, value, i); return; } } // i位置的Entry为null，表示该key还不存在，就把当前key、value放入i位置  // 线性探测法，这里的i不一定为最初的由hashcode计算后的i  tab[i] = new Entry(key, value); int sz = ++size; // 先进行启发式清理操作，随后判断是否需要进行rehash操作  if (!cleanSomeSlots(i, sz) \u0026\u0026 sz = threshold) rehash(); } private void rehash() { // rehash前进行一个全面的清理  expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis  // 这里判断条件将扩容的要求缩减为了3/4的threshold，初始构造时threshold为2/3的len  // 因此相当于扩容的要求为1/2的len  if (size = threshold - threshold / 4) resize(); } private void resize() { // 新哈希表的容量为原来的2倍  Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; // 将所有旧元素放到新的哈希表中  for (int j = 0; j  oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal k = e.get(); if (k == null) { // 旧元素中如果有已经成为stale entry的，直接将其value的引用断开  // 方便GC回收value占用的空间  e.value = null; // Help the GC  } else { // 线性探测法将旧元素放到新表中的合适位置  int h = k.threadLocalHashCode \u0026 (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } // 重新设置扩容阈值和当前元素个数，并将table指向新表，完成扩容操作  setThreshold(newLen); size = count; table = newTab; }     首先介绍一下 nextIndex 和 preIndex 方法，它们分别计算当前位置 i 的下一个位置和上一个位置，这种计算方式使得数组的位置得到了循环利用，逻辑上构成了一个环形数组，next 表示顺时针，而 pre 表示逆时针，如下图所示：\n\r\r  set 方法的主要作用是新增和修改哈希表中的元素，处理冲突的方式也是常用的线性探测法，即如果使用 Key（ThreadLocal 类型）的 threadLocalHashCode 计算出的位置已经存在 Entry（这个 Entry 有可能是有效的元素，也有可能是 Key 已经被回收的 stale entry），就进入循环，判断是否是修改操作。注意循环中还有个 replaceStaleEntry，它会执行一些清理工作，然后将 key、value 放到合适的 Entry 中，后面会详细介绍。一直探测到某个位置的 Entry 为 null，就用 key 、value 新建 Entry 并放在该位置。\n  rehash 操作前，会先进行一次 cleanSomeSlots 清理操作，这个方法在源码注释中使用了 Heuristically（启发式地） 进行描述，因此这里简称它为 启发式清理。而在 rehash 方法中，在调用 resize 方法扩容前，还会调用另外一个 expungeStaleEntries 清理操作，熟悉的词汇，在源码注释中描述为 Expunge all stale entries in the table（清理所有 stale entry），它本质上是调用了 expungeStaleEntry 方法，而 expungeStaleEntry 方法是对哈希表中的 stale entry 进行部分清理，后面就简称它为 分段式清理。\n  两个清理工作完成后，才开始正式的 resize 扩容流程，新建一个两倍容量的数组，将旧表中的元素转移到新表，同时清理一些 stale entry。\n  getEntry 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33  private Entry getEntry(ThreadLocal key) { // 计算key对应在哈希表中的实际位置i，作为查找的起点  int i = key.threadLocalHashCode \u0026 (table.length - 1); Entry e = table[i]; // 如果i位置的entry不为空，且直接就是要找的key，直接返回，提高效率  if (e != null \u0026\u0026 e.get() == key) return e; else // 否则，需要进一步查询  return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { Entry[] tab = table; int len = tab.length; // 当前entry不为空，可能是有效entry，也可能是stale entry  while (e != null) { ThreadLocal k = e.get(); // 找到了目标key，直接返回该Entry  if (k == key) return e; // k为null，表示该Entry是stale entry，以i为起点进行分段清理  if (k == null) expungeStaleEntry(i); else // 表示当前位置是有效entry，但不是目标entry，继续查找下一个位置  i = nextIndex(i, len); e = tab[i]; } // entry数组中的查找碰到null，表示查找失败，哈希表中不存在该key，返回null  return null; }   getEntry 的流程整体上比较简单，和普通线性探测哈希表的 get 方法没什么区别：\n 使用 key 的 threadLocalHashCode 计算出实际位置 i，以这个 i 为查找的起点，如果 i 位置的 Entry 就是我们想要查找的目标（e.get() == key），则直接返回。其实这里 e == null 时也可以直接返回 null，不过代码中把它延迟到了 getEntryAfterMiss 中，没什么区别。 getEntryAfterMiss 就从起点 i 开始，向后查找（nextIndex），如果找到目标，直接返回 Entry，如果遇到 null，直接返回 null 表示哈希表中没有该目标，这两个操作与普通线性探测法一致。不同的是当遇到 k == null，也就是 Entry 为 stale entry 时，需要多进行一次 分段式清理 操作。  remove 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  private void remove(ThreadLocal key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode \u0026 (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { // 找到目标  if (e.get() == key) { // 断开key的弱引用  e.clear(); // 以i为起点进行一次分段清理  expungeStaleEntry(i); return; } } }   线性探测法的 remove 操作其实是比较繁琐的，上面的代码看上去很简单，因为它把具体的操作放到了 分段式清理 的方法中，接下来就是要对清理方法进行分析。\n清理 从上面对几个增删改查操作的源码，不难发现，大多数方法除了完成自身的本职工作外，都会附带地在某些条件下对哈希表进行一些清理工作，包括 分段式清理 和 启发式清理，下面将分别进行分析。\n 分段式清理  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51  private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot  // 这步很简单，就是简单的删除staleSlot位置的entry  // 断开entry中指向value的强引用，以便value会被GC回收  tab[staleSlot].value = null; // 清空数组当前位置  tab[staleSlot] = null; size--; // Rehash until we encounter null  Entry e; int i; // 从被删除元素的下个位置开始，对每个Entry进行rehash操作，直到键簇的末尾(遇到null)  for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal k = e.get(); // 比起普通线性探测的删除，多了这个清理stale entry的操作  // k == null，表示当前entry为stale entry  if (k == null) { // 同样，断开value的强引用，将table  e.value = null; // 清空数组当前位置  tab[i] = null; size--; } else { // key不为空表示该entry有效，则进行rehash操作  // 重新计算位置  int h = k.threadLocalHashCode \u0026 (len - 1); // 新位置h与当前位置i不相等，表示它是因为哈希冲突被「挤」到i位置  // rehash后它有机会更靠近h位置  if (h != i) { // 这个操作很重要，表示将当前i位置留空，  // 保证rehash后，当前entry至少能再次放到这个i位置  tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until  // null because multiple entries could have been stale.  // 从h位置往后找到第一个为null的位置即为该entry的新位置  // 上面在i位置留了个空，因此最坏情况是最终h==i  while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }   这个清理基本上等同于普通线性探测法的删除操作，只是在 rehash 的过程中增加了一个删除 stale entry 的步骤。下面以一个示例对流程进行讲解：\n  初始状态：K1~K7 代表一个键簇，假定 K1~K7 计算后得到的位置均为 13。图中绿色表示有效 entry，灰色表示 stale entry，而白色为 null。现在开始执行 expungeStaleEntry(13)，即传入的参数 staleSlot = 13。\n\r\r  根据步骤，首先删除 K1 的 Entry，并将 i 移动到 K1 的下个位置 14：\n\r\r  随后，K2 位置为 stale entry，进入 k == null 分支，删除 K2，进入下次循环，i 到达 15，K3 为有效 entry，进行 rehash 操作，将 h 进行计算 h = 13（1 中的假设）。\n\r\r  先清空 i 位置，随后开始判断 h 位置，刚好 h 位置为空，则直接将 K3 代表的 Entry 放入 13 位置，i 移动到 0 位置。\n\r\r  与步骤 3 类似，清空 K4，i 移动至 1 位置。\n\r\r  K5~K7 均为有效 entry，因此进行 rehash 操作，K5 的 h = 13，此时 13 位置不为空，则 h 移动到 14，14 位置为空，则将 K5 的 Entry 移动到 14。同理，将 K6 和 K7 移动到 15 和 0 位置。最后，i 移动到 4 的位置（原 键簇末尾紧邻的 null 位置），返回 i（马上会用到），本次 分段式清理 结束。\n\r\r  了解过 expungeStaleEntry 基本原理后，回头看看 rehash 代码中调用的 expungeStaleEntries 方法：\n1 2 3 4 5 6 7 8 9 10 11 12 13  /** * Expunge all stale entries in the table. */ private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; // 遍历哈希表每个位置，对stale entry进行清理  for (int j = 0; j  len; j++) { Entry e = tab[j]; if (e != null \u0026\u0026 e.get() == null) expungeStaleEntry(j); } }   是不是就毫无难度了，这就是一个简单粗暴的全局大清理工作。\n 启发式清理  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40  /** * Heuristically scan some cells looking for stale entries. * This is invoked when either a new element is added, or * another stale one has been expunged. It performs a * logarithmic number of scans, as a balance between no * scanning (fast but retains garbage) and a number of scans * proportional to number of elements, that would find all * garbage but would cause some insertions to take O(n) time. * * @param i a position known NOT to hold a stale entry. The * scan starts at the element after i. * * @param n scan control: {@code log2(n)} cells are scanned, * unless a stale entry is found, in which case * {@code log2(table.length)-1} additional cells are scanned. * When called from insertions, this parameter is the number * of elements, but when from replaceStaleEntry, it is the * table length. (Note: all this could be changed to be either * more or less aggressive by weighting n instead of just * using straight log n. But this version is simple, fast, and * seems to work well.) * * @return true if any stale entries have been removed. */ private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null \u0026\u0026 e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ( (n = 1) != 0); return removed; }   这里把源码中的所有注释都搬进来了，非常详细的一段注释，从设计思想到各参数的详细讲解，应有尽有。代码不长，核心循环的工作是以 i 为起点对哈希表进行扫描（注释中重点写明这个起始 i 位置一定 不是 stale entry），判断是否存在 stale entry。如果一直没扫描到，那么在扫描 $log_2 n$ 次后就结束循环，返回 false。如果扫描到存在 stale entry，那么 cleanSomeSlots 调用我们刚介绍过的 expungeStaleEntry 进行清理，i 的值将直接跳到被清理键簇的紧邻 null 位置，并且会将扫描次数扩大，进行额外的 $log_2 (table.length)-1$ 次扫描。\n每次发现 stale entry，就会重新将扫描次数进行增加，哈希表中的 stale entry 越多，扫描的次数就会越多，进行的清理操作就越多，这就是一个逐步启发的过程。代码注释中说到这种方式是一种折中的实现，在完全不进行扫描和全局扫描之间找到一个平衡点。\n这个方法会在两个地方被调用，第一个是在 set 方法的末尾，新增元素成功后，在 rehash 之前进行一次启发式清理，这时候传入的两个参数分别为新增元素的位置 i 及新增后所有元素的个数 sz。\n1 2 3 4 5 6  // 在i位置新增entry元素 tab[i] = new Entry(key, value); int sz = ++size; // 先进行启发式清理操作，随后判断是否需要进行rehash操作 if (!cleanSomeSlots(i, sz) \u0026\u0026 sz = threshold) rehash();   第二个被调用的地方就是我们之前一笔带过的 replaceStaleEntry，这个方法逻辑比较复杂，涉及的内容比较多，因此我放到了最后再来补上。\n replaceStaleEntry  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57  private void replaceStaleEntry(ThreadLocal key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run.  // We clean out whole runs at a time to avoid continual  // incremental rehashing due to garbage collector freeing  // up refs in bunches (i.e., whenever the collector runs).  int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever  // occurs first  for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal k = e.get(); // If we find key, then we need to swap it  // with the stale entry to maintain hash table order.  // The newly stale slot, or any other stale slot  // encountered above it, can then be sent to expungeStaleEntry  // to remove or rehash all of the other entries in run.  if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists  if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the  // first stale entry seen while scanning for key is the  // first still present in the run.  if (k == null \u0026\u0026 slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot  tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them  if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }   这也是个非常繁琐的方法，但是注释内容较多，理解起来也很方便。\n  这个方法是在 set 中被调用的，在线性探测插入（或修改）元素时，如果遇到了 stale entry，那么就进入到 replaceStaleEntry，传入的参数为元素的 key、value 以及 stale entry 的位置 i。\n1 2 3 4 5  // k为null的情况，表示stale entry if (k == null) { replaceStaleEntry(key, value, i); return; }     replaceStaleEntry 中的第一个循环主要作用是找到 i 位置所在键簇最前端的某个 stale entry 位置。举例说明，set 方法将传入参数 K8，图中 K8 为待探测元素，计算得到它的起始位置为 0。由于 K4 为有效 entry，且 K4 ≠ K8，因此 set 方法中的 i 移动至 1 位置。1 位置上的 K5 是 stale entry，因此，从这里开始调用 replaceStaleEntry，传入的第三个参数 staleSlot 为 1。这时候，replaceStaleEntry 的第一个循环就从这个 staleSlot 开始 向前移动，寻找最前端的 stale slot，即 13（虽然 15 也是 stale slot，但它不是这个键簇的最前端），并赋值 slotToExpunge = 13。\n\r\r  第二个循环从 staleSlot 的下个位置开始，往后移动，在键簇中寻找 k == key 的 Entry，直到键簇末尾。注意循环末尾的一小段代码：\n1 2  if (k == null \u0026\u0026 slotToExpunge == staleSlot) slotToExpunge = i;   它表示如果在 往后（区别步骤 2 中的往前）寻找的过程中遇到了 stale entry，且刚才步骤 2 中没找到 stale entry，那么就将 slotToExpunge 赋值为这个 stale entry 的位置 i。再用一个例子来说明，如下图所示，同样从 set K8 元素开始，到 1 位置进入 replaceStaleEntry，此时往前寻找不到 stale entry，那么进入第二个循环前，slotToExpunge == staleSlot。\n进入第二个循环后，向后寻找到 2 位置，发现 K6 是 stale slot，即 k == null，且这时候满足第二个条件，因此 slotToExpunge = 2。\n\r\r这个赋值操作最多只会执行一次，第二次再进来 slotToExpunge == staleSlot 这个条件一定不会再满足了，这个循环的起始位置是 staleSlot 的 下个位置，已经就不等于 staleSlot 了，往后的 i 值就更不会满足该条件。\n  第二个循环过程中，如果找到了满足 k == key 条件的 Entry，那么就会进入替换及清理的代码中：\n1 2 3 4 5 6 7 8 9 10 11 12  if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists  if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; }   staleSlot 是调用 replaceStaleEntry 方法时传入的参数，也就是 set 方法调用过程中发现的第一个 stale entry 的位置。这里先将当前 Entry 的 value 进行了替换修改，然后将当前位置 i 与 staleSlot 位置的元素进行了交换，交换过后，i 位置变为 stale entry，而 staleSlot 位置成为了有效 entry。\n这段代码就是 replaceStaleEntry 命名的由来，它将原来 set 中识别出的 stale entry 替换为了一个新的有效 entry（key 是原来已经存在的，仅修改了 value）。下图中，K8 == K8'，当 i == 4 时，进入上述逻辑中，先将 K8' 的 value 进行替换修改，再将 K5 与 K8' 进行交换，得到下面的成果。\n\r\r\r\r替换成功后，随后条件判断与步骤 3 逻辑相同，都是确定 slotToExpunge 的位置，此时的 i 位置已经是 stale entry 了，因此可以作为 expungeStaleEntry 分段式清理 的起点。\n最后就是进行两次清理，先分段清理，再将其返回值传入 cleanSomeSlots 进行启发式清理，启发式清理中的第二个参数为 len，即哈希表当前的最大容量，区别 set 方法末尾的参数传入的 sz。\n  若第二个循环中没有找到能够替换的 Entry，则进入到最后的新建逻辑：\n1 2 3 4 5 6 7  // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);   staleSlot 处成为新元素插入的位置，如果在第二个循环中发现了其他 stale entry，就进行两步清理工作。\n   ThreadLocal 注意事项 内存泄漏 \r\r根据官方文档的推荐，我们平时使用 ThreadLocal 往往都会将它声名为 private static，那么，上图中红色部分的强引用将会一直存在（metaspace 中），该 ThreadLocal 在一个长期执行线程的 Thread.threadLocals 哈希表中对应的一个 Entry e，由于强引用的存在，e.get() 返回的 不会 是 null，那么指望上面的各种自动清理方法回收 value 内存就不太现实，需要开发人员手动调用 remove 方法回收不再使用的 ThreadLocal。\n脏数据 在线程池环境下，由于线程的复用，ThreadLocal 的脏数据问题比较常见。\n设想如下场景：用户 A 登录了网站，请求执行某些任务，为了后续方便，系统将部分用户信息保存到 ThreadLocal 中，但是忘记在任务完成后将这些信息手动清理；随后用户 B 也登录了同一个系统，执行了相同的任务，因为线程池中线程的复用，他居然获得到了用户 A 的某些信息，这显然是不行的。\n上述场景用简单的代码模拟如下：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56  public class ThreadDirtyData { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(1); executorService.execute(new UserTask(\"userA\")); executorService.execute(new UserTask(\"userB\")); TimeUnit.SECONDS.sleep(3); executorService.shutdownNow(); } } class UserData { String data; public UserData(String data) { this.data = data; } @Override public String toString() { return data; } } class UserContext { private static final ThreadLocalUserData CONTEXT = new ThreadLocal(); public static void set(UserData data) { CONTEXT.set(data); } public static UserData get() { return CONTEXT.get(); } public static void remove() { CONTEXT.remove(); } } class UserTask implements Runnable { private String userName; public UserTask(String name) { userName = name; } @Override public void run() { UserData userData = UserContext.get(); if (userData == null) { UserContext.set(new UserData(userName + \"'s data\")); userData = UserContext.get(); } System.out.println(userData); } }   执行结果：\n1 2  userA's data userA's data   即使执行我们执行的任务是用户 B 的 Task，但是还是获取到了 A 的数据。\n解决方案与内存泄漏相同，ThreadLocal 使用完，手动调用 remove 进行清理。\nThreadLocal 数据向子线程传递 ThreadLocal 数据对于它的子线程是不可见的，但很多场景下需要在子线程中使用父线程的数据，InheritableThreadLocal 由此而生。\n在 Thread 的 init 方法中有这么一段：\n1 2 3  if (inheritThreadLocals \u0026\u0026 parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);   而在父线程创建子线程时，会调用到这里的方法，从而将父线程 inheritableThreadLocals 中的所有元素拷贝给子线程的 inheritableThreadLocals。createInheritedMap 里面的内容比较简单，这里就不再深入了，感兴趣的可以自己去看看。\n但是在线程池的环境下，由于线程都已经自己创建好了，当任务从上游的父线程提交给线程池中的线程执行时，没有调用到上面的这个 init 过程，自然就没法向线程池中的线程传递数据了。针对这个问题，阿里提供了一个开源的 TransmittableThreadLocal，详细使用和原理这里就不展开了，有需要的可以自行查阅 官网。\n",
  "wordCount" : "3942",
  "inLanguage": "cn",
  "image":"https://i.loli.net/2021/09/25/HTBpP15DImOXl7A.jpg","datePublished": "2021-09-19T02:44:58+08:00",
  "dateModified": "2021-09-19T02:44:58+08:00",
  "author":[{
    "@type": "Person",
    "name": "SadBird"
  }],
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.liyangjie.cn/posts/work/threadlocal-source/"
  },
  "publisher": {
    "@type": "Organization",
    "name": "染竹君的个人博客",
    "logo": {
      "@type": "ImageObject",
      "url": "https://www.liyangjie.cn/favicon/favicon.ico"
    }
  }
}
</script>
</head>

<body class="">
    <a id="top"></a>
<script>
    if (localStorage.getItem("pref-theme") === "dark") {
        document.body.classList.add('dark');
    } else if (localStorage.getItem("pref-theme") === "light") {
        document.body.classList.remove('dark')
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        document.body.classList.add('dark');
    }

</script>

<header class="header">
    <nav class="nav">
        <div class="logo">
            <a href="https://www.liyangjie.cn/" accesskey="h" title="染竹君的小站 (Alt + H)">
                        
                    <img src="https://www.liyangjie.cn/img/logo_hue7837a3d2f79ccdd94da0537755daebc_15738_0x30_resize_box_3.png" alt="logo" aria-label="logo"
                        height="30">染竹君的小站</a>
            <span class="logo-switches">
                <button id="theme-toggle" accesskey="t" title="(Alt + T)">
                    <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                    </svg>
                    <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <circle cx="12" cy="12" r="5"></circle>
                        <line x1="12" y1="1" x2="12" y2="3"></line>
                        <line x1="12" y1="21" x2="12" y2="23"></line>
                        <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                        <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                        <line x1="1" y1="12" x2="3" y2="12"></line>
                        <line x1="21" y1="12" x2="23" y2="12"></line>
                        <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                        <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
                    </svg>
                </button>
            </span>
        </div>
        <ul id="menu">
            <li>
                <a href="https://www.liyangjie.cn/categories/" title="分类">
                    <span>分类</span>
                </a>
            </li>
            <li>
                <a href="https://www.liyangjie.cn/tags/" title="标签">
                    <span>标签</span>
                </a>
            </li>
            <li>
                <a href="https://www.liyangjie.cn/archives/" title="归档">
                    <span>归档</span>
                </a>
            </li>
            <li>
                <a href="https://www.liyangjie.cn/search/" title="搜索">
                    <span>搜索</span>
                </a>
            </li>
        </ul>
    </nav>
</header>
<main class="main">

<article class="post-single">
  <header class="post-header">
    <div class="breadcrumbs"><a href="https://www.liyangjie.cn/">Home</a>&nbsp;»&nbsp;<a href="https://www.liyangjie.cn/posts/">Posts</a>&nbsp;»&nbsp;<a href="https://www.liyangjie.cn/posts/work/">Work</a></div>
    <h1 class="post-title">
      ThreadLocal 分析（下）——使用和源码
    </h1>
    <div class="post-meta"><span title='2021-09-19 02:44:58 +0800 CST'>September 19, 2021</span>&nbsp;·&nbsp;19 min&nbsp;·&nbsp;SadBird&nbsp;|&nbsp;<a href="https://github.com/YazidLee/hugo-backup/tree/master/content/posts/work/ThreadLocal%e5%88%86%e6%9e%90%28%e4%b8%8b%29--%e4%bd%bf%e7%94%a8%e5%92%8c%e6%ba%90%e7%a0%81.md" rel="noopener noreferrer" target="_blank">Suggest Changes</a>
</div>
  </header> <figure class="entry-cover"><img class="lazy" src="/svg/loading.min.svg"  data-src="https://i.loli.net/2021/09/25/HTBpP15DImOXl7A.jpg" loading="lazy" alt="">
        
</figure><div class="toc">
    <details >
        <summary accesskey="c" title="(Alt + C)">
            <span class="details">Table of Contents</span>
        </summary>

        <div class="inner"><ul>
                <li>
                    <a href="#threadlocal-%e5%9f%ba%e6%9c%ac%e4%bd%bf%e7%94%a8" aria-label="ThreadLocal 基本使用">ThreadLocal 基本使用</a></li>
                <li>
                    <a href="#threadlocal-%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90" aria-label="ThreadLocal 源码分析">ThreadLocal 源码分析</a><ul>
                        
                <li>
                    <a href="#threadlocal-%e6%a6%82%e8%a7%88" aria-label="ThreadLocal 概览">ThreadLocal 概览</a></li>
                <li>
                    <a href="#%e4%bd%9c%e4%b8%ba%e5%93%88%e5%b8%8c%e8%a1%a8%e7%9a%84-key" aria-label="作为哈希表的 Key">作为哈希表的 Key</a></li>
                <li>
                    <a href="#%e7%ae%a1%e7%90%86-threadlocals" aria-label="管理 threadLocals">管理 threadLocals</a></li></ul>
                </li>
                <li>
                    <a href="#threadlocalmap-%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90" aria-label="ThreadLocalMap 源码分析">ThreadLocalMap 源码分析</a><ul>
                        
                <li>
                    <a href="#threadmap-%e5%ad%97%e6%ae%b5" aria-label="ThreadMap 字段">ThreadMap 字段</a></li>
                <li>
                    <a href="#set" aria-label="set">set</a></li>
                <li>
                    <a href="#getentry" aria-label="getEntry">getEntry</a></li>
                <li>
                    <a href="#remove" aria-label="remove">remove</a></li>
                <li>
                    <a href="#%e6%b8%85%e7%90%86" aria-label="清理">清理</a></li></ul>
                </li>
                <li>
                    <a href="#threadlocal-%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9" aria-label="ThreadLocal 注意事项">ThreadLocal 注意事项</a><ul>
                        
                <li>
                    <a href="#%e5%86%85%e5%ad%98%e6%b3%84%e6%bc%8f" aria-label="内存泄漏">内存泄漏</a></li>
                <li>
                    <a href="#%e8%84%8f%e6%95%b0%e6%8d%ae" aria-label="脏数据">脏数据</a></li>
                <li>
                    <a href="#threadlocal-%e6%95%b0%e6%8d%ae%e5%90%91%e5%ad%90%e7%ba%bf%e7%a8%8b%e4%bc%a0%e9%80%92" aria-label="ThreadLocal 数据向子线程传递">ThreadLocal 数据向子线程传递</a>
                </li>
            </ul>
            </li>
            </ul>
        </div>
    </details>
</div>

  <div class="post-content"><h2 id="threadlocal-基本使用">ThreadLocal 基本使用<a hidden class="anchor" aria-hidden="true" href="#threadlocal-基本使用">#</a></h2>
<p>下面示例模拟两个请求，在两个线程完成任务，任务由两部分组成，其中 <code>Service1</code> 负责生成 <code>TRACE_ID</code> 和一部分任务并调用 <code>Service2</code> ，而 <code>Service2</code> 可以使用 <code>TRACE_ID</code> ，完成剩余部分任务，最后清理 <code>TRACE_ID</code> 。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ThreadLocalDemo</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Service2</span> <span class="n">service2</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Service2</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">Service1</span> <span class="n">service1</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Service1</span><span class="o">(</span><span class="n">service2</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// 两个线程模拟两个请求
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">Thread</span> <span class="n">request1</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="o">(</span><span class="n">service1</span><span class="o">::</span><span class="n">service1</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Thread</span> <span class="n">request2</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="o">(</span><span class="n">service1</span><span class="o">::</span><span class="n">service1</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">request1</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">request2</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 全局上下文，管理TRACE_ID
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">TraceContext</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// API文档中推荐ThreadLocal声明为private static
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">ThreadLocal</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">CONTEXT</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ThreadLocal</span><span class="o">&lt;&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">set</span><span class="o">(</span><span class="n">String</span> <span class="n">traceId</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">CONTEXT</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">traceId</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="n">String</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">CONTEXT</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">remove</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">CONTEXT</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 负责创建TRACE_ID，调用Service2
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">Service1</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">String</span> <span class="n">TRACE_ID_PREFIX</span> <span class="o">=</span> <span class="s">&#34;X-TRACE-ID-&#34;</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">AtomicInteger</span> <span class="n">ID</span> <span class="o">=</span> <span class="k">new</span> <span class="n">AtomicInteger</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">Service2</span> <span class="n">service2</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="nf">Service1</span><span class="o">(</span><span class="n">Service2</span> <span class="n">service2</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="o">.</span><span class="na">service2</span> <span class="o">=</span> <span class="n">service2</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">service1</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 在service1中先设置TRACE_ID
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">TraceContext</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">TRACE_ID_PREFIX</span> <span class="o">+</span> <span class="n">ID</span><span class="o">.</span><span class="na">getAndIncrement</span><span class="o">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 打印新生成的TRACE_ID，模拟一些业务操作
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;generate new TRACE_ID: &#34;</span> <span class="o">+</span> <span class="n">TraceContext</span><span class="o">.</span><span class="na">get</span><span class="o">()</span>
</span></span><span class="line"><span class="cl">                <span class="o">+</span> <span class="s">&#34; -&gt; do something in service1, current thread is: &#34;</span> <span class="o">+</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 再调用service2
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">service2</span><span class="o">.</span><span class="na">service2</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 使用TRACE_ID，并完成清理工作
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">Service2</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">service2</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 打印TRACE_ID， 模拟对TRACE_ID的操作
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">&#34;current TRACE_ID is: &#34;</span> <span class="o">+</span> <span class="n">TraceContext</span><span class="o">.</span><span class="na">get</span><span class="o">()</span>
</span></span><span class="line"><span class="cl">                <span class="o">+</span> <span class="s">&#34; -&gt; do something in service2, current thread is: &#34;</span> <span class="o">+</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// 操作完成后，手动清理
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">TraceContext</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>以上就是 <code>ThreadLocal</code> 最基本的使用场景，通过 <code>ThreadLocal</code> 来透传全局的某些上下文信息，以便后续的分析和追踪（<code>logback</code> 中实现 <code>MDC</code> 正是使用了 <code>ThreadLocal</code>）。</p>
<hr>
<h2 id="threadlocal-源码分析">ThreadLocal 源码分析<a hidden class="anchor" aria-hidden="true" href="#threadlocal-源码分析">#</a></h2>
<h3 id="threadlocal-概览">ThreadLocal 概览<a hidden class="anchor" aria-hidden="true" href="#threadlocal-概览">#</a></h3>
<p>在解释具体代码之前，首先要搞清楚 <code>ThreadLocal</code>、<code>Thread</code> 及 <code>ThreadLocal</code> 中实际保存的 <code>value</code> 的关系，下面是 <code>ThreadLocal</code> 源码中的一段注释：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**                                                               
</span></span></span><span class="line"><span class="cl"><span class="cm"> * ThreadLocals rely on per-thread linear-probe hash maps attached
</span></span></span><span class="line"><span class="cl"><span class="cm"> * to each thread (Thread.threadLocals and                        
</span></span></span><span class="line"><span class="cl"><span class="cm"> * inheritableThreadLocals).  The ThreadLocal objects act as keys,
</span></span></span><span class="line"><span class="cl"><span class="cm"> * searched via threadLocalHashCode.  This is a custom hash code  
</span></span></span><span class="line"><span class="cl"><span class="cm"> * (useful only within ThreadLocalMaps) that eliminates collisions
</span></span></span><span class="line"><span class="cl"><span class="cm"> * in the common case where consecutively constructed ThreadLocals
</span></span></span><span class="line"><span class="cl"><span class="cm"> * are used by the same threads, while remaining well-behaved in  
</span></span></span><span class="line"><span class="cl"><span class="cm"> * less common cases.                                             
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>                                                               
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">threadLocalHashCode</span> <span class="o">=</span> <span class="n">nextHashCode</span><span class="o">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>这里面有几个关键信息：</p>
<ol>
<li><code>ThreadLocal</code> 机制依靠的是 <code>Thread.threadLocals</code> 和 <code>Thread.inheritableThreadLocals</code> 这两个哈希表。</li>
<li>每个线程有自己的 <code>Thread.threadLocals</code> 和 <code>Thread.inheritableThreadLocals</code>，线程间通过这种方式避免了共享，实现了隔离。</li>
<li><code>ThreadLocal</code> 自身的作用是作为 <code>Thread.threadLocals</code> 和 <code>Thread.inheritableThreadLocals</code> 的 Key，因此每个 <code>ThreadLocal</code> 都需要有自己的hashcode，即 <code>threadLocalHashCode</code>。</li>
<li><code>Thread.threadLocals</code> 和 <code>Thread.inheritableThreadLocals</code> 处理冲突的方式为 <code>linear-probe</code>，即线性探测。</li>
</ol>
<p>在此基础上，我们先去 <code>Thread</code> 中找到 <code>Thread.threadLocals</code> 和 <code>Thread.inheritableThreadLocals</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/* ThreadLocal values pertaining to this thread. This map is maintained
</span></span></span><span class="line"><span class="cl"><span class="cm"> * by the ThreadLocal class. */</span>                                        
</span></span><span class="line"><span class="cl"><span class="n">ThreadLocal</span><span class="o">.</span><span class="na">ThreadLocalMap</span> <span class="n">threadLocals</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>                        
</span></span><span class="line"><span class="cl">                                                                       
</span></span><span class="line"><span class="cl"><span class="cm">/*                                                                     
</span></span></span><span class="line"><span class="cl"><span class="cm"> * InheritableThreadLocal values pertaining to this thread. This map is
</span></span></span><span class="line"><span class="cl"><span class="cm"> * maintained by the InheritableThreadLocal class.                     
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>                                                                    
</span></span><span class="line"><span class="cl"><span class="n">ThreadLocal</span><span class="o">.</span><span class="na">ThreadLocalMap</span> <span class="n">inheritableThreadLocals</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>现在，可以画出一个大致的关系草图如下，先以 <code>threadLocals</code> 为例，<code>inheritableThreadLocals</code> 原理与 <code>threadLocals</code> 相同：</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/NeXLKPmW4vCJbcM.png" title="https://i.loli.net/2021/09/25/NeXLKPmW4vCJbcM.png" data-thumbnail="https://i.loli.net/2021/09/25/NeXLKPmW4vCJbcM.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/NeXLKPmW4vCJbcM.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/NeXLKPmW4vCJbcM.png"
            alt="https://i.loli.net/2021/09/25/NeXLKPmW4vCJbcM.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>从图上能更直观地看出 <code>ThreadLocal</code> 的「地位」，在层次结构上，它只是作为 <code>Thread</code> 中一个哈希表的 Key。但它的功能可不仅仅是个 Key，再回头看看 <code>threadLocals</code> 源码注释：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/* ThreadLocal values pertaining to this thread. This map is maintained
</span></span></span><span class="line"><span class="cl"><span class="cm"> * by the ThreadLocal class. */</span>                                        
</span></span><span class="line"><span class="cl"><span class="n">ThreadLocal</span><span class="o">.</span><span class="na">ThreadLocalMap</span> <span class="n">threadLocals</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>                                                                  
</span></span></code></pre></td></tr></table>
</div>
</div><p>不难看出 <code>ThreadLocal</code> 还肩负着维护 <code>threadLocals</code> 的重要使命，即对 <code>threadLocals</code> 进行增删改查等操作。 下面就对 <code>ThreadLocal</code> 的这两大作用分别进行源码分析。</p>
<h3 id="作为哈希表的-key">作为哈希表的 Key<a hidden class="anchor" aria-hidden="true" href="#作为哈希表的-key">#</a></h3>
<p>上面已经提到，每个 <code>ThreadLocal</code> 都有 <code>threadLocalHashCode</code> 属性，这个值将作为 Key 的 hashcode 参与到后续的计算。<code>threadLocalHashCode</code> 的计算方式如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">threadLocalHashCode</span> <span class="o">=</span> <span class="n">nextHashCode</span><span class="o">();</span>              
</span></span><span class="line"><span class="cl">                                                                     
</span></span><span class="line"><span class="cl"><span class="cm">/**                                                                  
</span></span></span><span class="line"><span class="cl"><span class="cm"> * The next hash code to be given out. Updated atomically. Starts at 
</span></span></span><span class="line"><span class="cl"><span class="cm"> * zero.                                                             
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>                                                                  
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">AtomicInteger</span> <span class="n">nextHashCode</span> <span class="o">=</span>                          
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">AtomicInteger</span><span class="o">();</span>                                             
</span></span><span class="line"><span class="cl">                                                                     
</span></span><span class="line"><span class="cl"><span class="cm">/**                                                                  
</span></span></span><span class="line"><span class="cl"><span class="cm"> * The difference between successively generated hash codes - turns  
</span></span></span><span class="line"><span class="cl"><span class="cm"> * implicit sequential thread-local IDs into near-optimally spread   
</span></span></span><span class="line"><span class="cl"><span class="cm"> * multiplicative hash values for power-of-two-sized tables.         
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>                                                                  
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">HASH_INCREMENT</span> <span class="o">=</span> <span class="n">0x61c88647</span><span class="o">;</span>                
</span></span><span class="line"><span class="cl">                                                                     
</span></span><span class="line"><span class="cl"><span class="cm">/**                                                                  
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Returns the next hash code.                                       
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>                                                                  
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">nextHashCode</span><span class="o">()</span> <span class="o">{</span>                                  
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">nextHashCode</span><span class="o">.</span><span class="na">getAndAdd</span><span class="o">(</span><span class="n">HASH_INCREMENT</span><span class="o">);</span>                   
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>可以看到，第1个 <code>ThreadLocal</code> 的 <code>threadLocalHashCode</code> 为 0，此后，每新建一个 <code>ThreadLocal</code> 对象，该对象的 <code>threadLocalHashCode</code> 值就为上一个对象的 <code>threadLocalHashCode</code> 值加上 <code>HASH_INCREMENT</code>。</p>
<p>说得直白点，设 <code>HASH_INCREMENT</code> 值为 $a$，那么第 1 个 <code>ThreadLocal</code> 对象的 <code>threadLocalHashCode</code> 为 $0 * a$，第 2 个为 $1 * a$，第 3 个为 $2 * a$，&hellip; ，第 n 个为 $(n - 1) * a$，属于乘法 hash。</p>
<p>代码中，这个 $a$ 值设定为一个特殊的数字：<code>0x61c88647</code>，理由在注释中已经给出，这个值能够使 Key 值在大小为 $2 ^ n$ 的哈希表上均匀地分布，至于其中的原理就不继续深究，和黄金分割、斐波那契相关，感兴趣的可以自行查阅资料。</p>
<p>继续查看 <code>ThreadLocal</code> 的静态内部类 <code>ThreadLocalMap</code>，它在构造函数中将 Key 的 hashcode 映射到具体位置的代码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">INITIAL_CAPACITY</span> <span class="o">=</span> <span class="n">16</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">key</span><span class="o">.</span><span class="na">threadLocalHashCode</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">INITIAL_CAPACITY</span> <span class="o">-</span> <span class="n">1</span><span class="o">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>为了证明这种方式的有效性，下面进行一个小的模拟实验：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">A</span> <span class="o">=</span> <span class="n">0x61c88647</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hashSequence</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">hashSequence</span><span class="o">(</span><span class="n">2</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">hashSequence</span><span class="o">(</span><span class="n">3</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">hashSequence</span><span class="o">(</span><span class="n">4</span><span class="o">);</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span> 
</span></span><span class="line"><span class="cl">                      
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">hashSequence</span><span class="o">(</span><span class="kt">int</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>                       
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">1</span> <span class="o">&lt;&lt;</span> <span class="n">n</span><span class="o">;</span>                                         
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">mod</span> <span class="o">=</span> <span class="n">size</span> <span class="o">-</span> <span class="n">1</span><span class="o">;</span>                                        
</span></span><span class="line"><span class="cl">                                                               
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span><span class="s">&#34;hash seq for &#34;</span> <span class="o">+</span> <span class="n">size</span> <span class="o">+</span> <span class="s">&#34; size table: &#34;</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">                                                               
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>                           
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="o">(</span><span class="n">i</span> <span class="o">*</span> <span class="n">A</span><span class="o">)</span> <span class="o">&amp;</span> <span class="n">mod</span><span class="o">;</span>                             
</span></span><span class="line"><span class="cl">        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">print</span><span class="o">(</span> <span class="n">index</span> <span class="o">+</span> <span class="s">&#34;  &#34;</span><span class="o">);</span>                       
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                                          
</span></span><span class="line"><span class="cl">                                                               
</span></span><span class="line"><span class="cl">    <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">();</span>                                      
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>结果如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">hash</span> seq <span class="k">for</span> <span class="m">2</span> size table: <span class="m">0</span>  <span class="m">1</span>  
</span></span><span class="line"><span class="cl"><span class="nb">hash</span> seq <span class="k">for</span> <span class="m">4</span> size table: <span class="m">0</span>  <span class="m">3</span>  <span class="m">2</span>  <span class="m">1</span>  
</span></span><span class="line"><span class="cl"><span class="nb">hash</span> seq <span class="k">for</span> <span class="m">8</span> size table: <span class="m">0</span>  <span class="m">7</span>  <span class="m">6</span>  <span class="m">5</span>  <span class="m">4</span>  <span class="m">3</span>  <span class="m">2</span>  <span class="m">1</span>  
</span></span><span class="line"><span class="cl"><span class="nb">hash</span> seq <span class="k">for</span> <span class="m">16</span> size table: <span class="m">0</span>  <span class="m">7</span>  <span class="m">14</span>  <span class="m">5</span>  <span class="m">12</span>  <span class="m">3</span>  <span class="m">10</span>  <span class="m">1</span>  <span class="m">8</span>  <span class="m">15</span>  <span class="m">6</span>  <span class="m">13</span>  <span class="m">4</span>  <span class="m">11</span>  <span class="m">2</span>  <span class="m">9</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>效果拔群！</p>
<h3 id="管理-threadlocals">管理 threadLocals<a hidden class="anchor" aria-hidden="true" href="#管理-threadlocals">#</a></h3>
<ul>
<li>
<p>增、改：<code>set</code> 方法源码如下，关于 <code>ThreadLocalMap</code> 放在后面探讨，这里先简单理解为一个普通的哈希表：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">set</span><span class="o">(</span><span class="n">T</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>  
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取当前线程          
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取线程中的threadLocals
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">ThreadLocalMap</span> <span class="n">map</span> <span class="o">=</span> <span class="n">getMap</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>   
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">(</span><span class="n">map</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 不为空直接set，set方法其实是有“副作用”的，但这里暂时理解为简单的取值   
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// 这里this就是当前的ThreadLocal对象，作为Key               
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">map</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>         
</span></span><span class="line"><span class="cl">    <span class="k">else</span>    
</span></span><span class="line"><span class="cl">        <span class="c1">// 为空就new Map                          
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">createMap</span><span class="o">(</span><span class="n">t</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>          
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ThreadLocalMap</span> <span class="nf">getMap</span><span class="o">(</span><span class="n">Thread</span> <span class="n">t</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">t</span><span class="o">.</span><span class="na">threadLocals</span><span class="o">;</span>       
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">createMap</span><span class="o">(</span><span class="n">Thread</span> <span class="n">t</span><span class="o">,</span> <span class="n">T</span> <span class="n">firstValue</span><span class="o">)</span> <span class="o">{</span>                       
</span></span><span class="line"><span class="cl">    <span class="n">t</span><span class="o">.</span><span class="na">threadLocals</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ThreadLocalMap</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">firstValue</span><span class="o">);</span>     
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>查：<code>get</code> 方法源码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">T</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span>       
</span></span><span class="line"><span class="cl">    <span class="c1">// 这两行和set一模一样                               
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>                
</span></span><span class="line"><span class="cl">    <span class="n">ThreadLocalMap</span> <span class="n">map</span> <span class="o">=</span> <span class="n">getMap</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>                   
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">(</span><span class="n">map</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// map不为空，直接从map中取值，getEntry其实是有「副作用」的，但这里暂时理解为简单的取值                                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">ThreadLocalMap</span><span class="o">.</span><span class="na">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">map</span><span class="o">.</span><span class="na">getEntry</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>  
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>                              
</span></span><span class="line"><span class="cl">            <span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">&#34;unchecked&#34;</span><span class="o">)</span>            
</span></span><span class="line"><span class="cl">            <span class="n">T</span> <span class="n">result</span> <span class="o">=</span> <span class="o">(</span><span class="n">T</span><span class="o">)</span><span class="n">e</span><span class="o">.</span><span class="na">value</span><span class="o">;</span>                    
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">result</span><span class="o">;</span>                            
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                             
</span></span><span class="line"><span class="cl">    <span class="o">}</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">// map为空，则需要初始化map                                                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="n">setInitialValue</span><span class="o">();</span>                         
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 这个方法和set基本一模一样
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">private</span> <span class="n">T</span> <span class="nf">setInitialValue</span><span class="o">()</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">// 获取初始值        
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">T</span> <span class="n">value</span> <span class="o">=</span> <span class="n">initialValue</span><span class="o">();</span>         
</span></span><span class="line"><span class="cl">    <span class="n">Thread</span> <span class="n">t</span> <span class="o">=</span> <span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">ThreadLocalMap</span> <span class="n">map</span> <span class="o">=</span> <span class="n">getMap</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>  
</span></span><span class="line"><span class="cl">    <span class="c1">// 将初始值设置到map中 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="o">(</span><span class="n">map</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>   
</span></span><span class="line"><span class="cl">        <span class="c1">// set方法其实是有「副作用」的，但这里暂时理解为简单的取值 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// 这里this就是当前的ThreadLocal对象，作为Key                 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">map</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>         
</span></span><span class="line"><span class="cl">    <span class="k">else</span>                              
</span></span><span class="line"><span class="cl">        <span class="n">createMap</span><span class="o">(</span><span class="n">t</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>          
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">value</span><span class="o">;</span>                     
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 待子类重写，返回初始value
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">protected</span> <span class="n">T</span> <span class="nf">initialValue</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>            
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>删：<code>remove</code> 方法源码如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kt">void</span> <span class="nf">remove</span><span class="o">()</span> <span class="o">{</span>                                
</span></span><span class="line"><span class="cl">    <span class="n">ThreadLocalMap</span> <span class="n">m</span> <span class="o">=</span> <span class="n">getMap</span><span class="o">(</span><span class="n">Thread</span><span class="o">.</span><span class="na">currentThread</span><span class="o">());</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">(</span><span class="n">m</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>   
</span></span><span class="line"><span class="cl">        <span class="c1">// 这里this就是当前的ThreadLocal对象，作为Key传入，最终从map中删除Key为当前ThreadLocal的元素 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// 这里是线性探测法的remove，需要特别注意
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// 与get、set类似，这里的remove也会有特殊的操作，这里暂时理解为简单的删除                               
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">m</span><span class="o">.</span><span class="na">remove</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>                               
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>Java8 新增静态方法 <code>withInitial</code> ：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="o">&lt;</span><span class="n">S</span><span class="o">&gt;</span> <span class="n">ThreadLocal</span><span class="o">&lt;</span><span class="n">S</span><span class="o">&gt;</span> <span class="nf">withInitial</span><span class="o">(</span><span class="n">Supplier</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="n">S</span><span class="o">&gt;</span> <span class="n">supplier</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">new</span> <span class="n">SuppliedThreadLocal</span><span class="o">&lt;&gt;(</span><span class="n">supplier</span><span class="o">);</span>                               
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="kd">final</span> <span class="kd">class</span> <span class="nc">SuppliedThreadLocal</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="kd">extends</span> <span class="n">ThreadLocal</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">final</span> <span class="n">Supplier</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="n">T</span><span class="o">&gt;</span> <span class="n">supplier</span><span class="o">;</span>                 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">SuppliedThreadLocal</span><span class="o">(</span><span class="n">Supplier</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="n">T</span><span class="o">&gt;</span> <span class="n">supplier</span><span class="o">)</span> <span class="o">{</span>         
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="o">.</span><span class="na">supplier</span> <span class="o">=</span> <span class="n">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">supplier</span><span class="o">);</span>         
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                                             
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@Override</span>                                                     
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="n">T</span> <span class="nf">initialValue</span><span class="o">()</span> <span class="o">{</span>                                  
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">supplier</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>                                    
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                                             
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>也比较容易理解，原来的写法是：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ThreadLocal</span><span class="o">&lt;</span><span class="n">Object</span><span class="o">&gt;</span> <span class="n">tl</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ThreadLocal</span><span class="o">&lt;&gt;()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="nd">@Override</span>
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="n">Object</span> <span class="nf">initialValue</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="n">Object</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>现在可以写成这样，比较省事：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ThreadLocal</span><span class="o">&lt;</span><span class="n">Object</span><span class="o">&gt;</span> <span class="n">tl</span> <span class="o">=</span> <span class="n">ThreadLocal</span><span class="o">.</span><span class="na">withInitial</span><span class="o">(</span><span class="n">Object</span><span class="o">::</span><span class="k">new</span><span class="o">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<p>OK，至此，<code>ThreadLocal</code> 表面上的东西已经介绍得差不多了，代码都比较简单，结合上面那个草图理解起来应该没什么问题。然而，<code>ThreadLocal</code> 最为复杂的部分其实是它的内部类 <code>ThreadLocalMap</code>，下面的内容就是把这块硬骨头一点一点啃下来。</p>
<hr>
<h2 id="threadlocalmap-源码分析">ThreadLocalMap 源码分析<a hidden class="anchor" aria-hidden="true" href="#threadlocalmap-源码分析">#</a></h2>
<p>在进入源码前，需要有一些知识铺垫：</p>
<ol>
<li>
<p>首先需要了解过哈希表是什么，对哈希冲突、开放地址、线性探测等概念比较熟悉，最好自己动手实现过。可以上网找找，资料挺多的，这里推荐一个入门视频：</p>
<div class="bilibili">
    <iframe src="https://player.bilibili.com/player.html?bvid=BV1MC4y1p7rP&page=1&as_wide=1&high_quality=1&danmaku=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="Bilibili Video" loading="lazy"></iframe>
</div>
</li>
<li>
<p>对 Java 的弱引用有所了解，不知道的可以看看之前的这篇文章 <a href="https://www.liyangjie.cn/posts/work/threadlocal-reference/">ThreadLocal 分析（上）——Java 中的引用</a>。</p>
</li>
</ol>
<h3 id="threadmap-字段">ThreadMap 字段<a hidden class="anchor" aria-hidden="true" href="#threadmap-字段">#</a></h3>
<p>初步先看看 <code>ThreadLocalMap</code> 的字段：</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/q8mJRXyKC2hwM4S.png" title="https://i.loli.net/2021/09/25/q8mJRXyKC2hwM4S.png" data-thumbnail="https://i.loli.net/2021/09/25/q8mJRXyKC2hwM4S.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/q8mJRXyKC2hwM4S.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/q8mJRXyKC2hwM4S.png"
            alt="https://i.loli.net/2021/09/25/q8mJRXyKC2hwM4S.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>阅读过 <code>HashMap</code> 源码的话其实这些字段都不需要再解释了，非常简单，从上到下依次为：初始容量（最大桶数量）、实际的哈希表（<code>Entry</code> 数组，它的长度一定为 $2 ^ n$）、当前哈希表中元素的数量、下次扩容的阈值。</p>
<p>其他字段都好说，这里引入关键问题的字段就是 <code>Entry</code>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> *... 
</span></span></span><span class="line"><span class="cl"><span class="cm"> * To help deal with very large and long-lived usages, the hash table entries use
</span></span></span><span class="line"><span class="cl"><span class="cm"> * WeakReferences for keys.
</span></span></span><span class="line"><span class="cl"><span class="cm"> *...
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="kd">class</span> <span class="nc">ThreadLocalMap</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="cm">/**                                                               
</span></span></span><span class="line"><span class="cl"><span class="cm">    * The entries in this hash map extend WeakReference, using       
</span></span></span><span class="line"><span class="cl"><span class="cm">    * its main ref field as the key (which is always a               
</span></span></span><span class="line"><span class="cl"><span class="cm">    * ThreadLocal object).  Note that null keys (i.e. entry.get()    
</span></span></span><span class="line"><span class="cl"><span class="cm">    * == null) mean that the key is no longer referenced, so the     
</span></span></span><span class="line"><span class="cl"><span class="cm">    * entry can be expunged from table.  Such entries are referred to
</span></span></span><span class="line"><span class="cl"><span class="cm">    * as &#34;stale entries&#34; in the code that follows.                   
</span></span></span><span class="line"><span class="cl"><span class="cm">    */</span>                                                               
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Entry</span> <span class="kd">extends</span> <span class="n">WeakReference</span><span class="o">&lt;</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;&gt;</span> <span class="o">{</span>        
</span></span><span class="line"><span class="cl">        <span class="cm">/** The value associated with this ThreadLocal. */</span>            
</span></span><span class="line"><span class="cl">        <span class="n">Object</span> <span class="n">value</span><span class="o">;</span>                                                 
</span></span><span class="line"><span class="cl">                                                                          
</span></span><span class="line"><span class="cl">        <span class="n">Entry</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">k</span><span class="o">,</span> <span class="n">Object</span> <span class="n">v</span><span class="o">)</span> <span class="o">{</span>                           
</span></span><span class="line"><span class="cl">            <span class="kd">super</span><span class="o">(</span><span class="n">k</span><span class="o">);</span>                                                 
</span></span><span class="line"><span class="cl">            <span class="n">value</span> <span class="o">=</span> <span class="n">v</span><span class="o">;</span>                                                
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                                             
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>有没有似曾相识的感觉，在上一篇 <a href="https://www.liyangjie.cn/posts/work/threadlocal-reference/">ThreadLocal 分析（上）——Java 中的引用</a> 中，介绍过一个 <code>WeakHashMap</code>，它的 <code>Entry</code> 定义 <code>private static class Entry&lt;K,V&gt; extends WeakReference&lt;Object&gt; ...</code> 与这里的 <code>Entry</code> 如出一辙，第一段注释也写得很清楚，使用 <code>WeakReference</code> 作为 Key 是为了回收生命周期较长的大对象。</p>
<p>留意第二段注释中有个特别的说明：「当某个 <code>entry</code> 满足 <code>entry.get() == null</code> 时（隐含条件是 <code>entry != null</code>），表明这个 <code>entry</code> 的 Key 已经不再被引用关联到，因此这个 <code>entry</code> 可以被的删除（<code>expunged</code>），这样的 <code>entry</code> 在代码中被称为 <code>stale entry</code>。」多看几遍这几个重要的单词，<code>expunged</code>、<code>stale entries</code>，后面会频繁出现。</p>
<p>现在可以将第一个草图进行修改了，哈希表 <code>ThreaedLoclaMap</code> 中 <code>Entry</code> 的Key实际上是一个 <code>WeakReference</code> 对象，这个对象中的 <code>referent</code> 弱指向了实际的 <code>ThreadLocal</code> 对象，虚线表示弱引用：</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/f1TJCLbvGpXl6nF.png" title="https://i.loli.net/2021/09/25/f1TJCLbvGpXl6nF.png" data-thumbnail="https://i.loli.net/2021/09/25/f1TJCLbvGpXl6nF.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/f1TJCLbvGpXl6nF.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/f1TJCLbvGpXl6nF.png"
            alt="https://i.loli.net/2021/09/25/f1TJCLbvGpXl6nF.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>接下来看看 <code>ThreadLocalMap</code> 的构造函数（在 <code>ThreadLocal</code> 的 <code>createMap</code> 方法中使用到，忘记的话可以退回到上一节的 <code>set</code> 方法中查看）：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">INITIAL_CAPACITY</span> <span class="o">=</span> <span class="n">16</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="n">ThreadLocalMap</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">firstKey</span><span class="o">,</span> <span class="n">Object</span> <span class="n">firstValue</span><span class="o">)</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">// 创建初始长度为16的Entry数组     
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">table</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="o">[</span><span class="n">INITIAL_CAPACITY</span><span class="o">];</span>  
</span></span><span class="line"><span class="cl">    <span class="c1">// 将传入的 ThreadLocal作为key， Object作为value，新建第一个Entry放入哈希表中                        
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">firstKey</span><span class="o">.</span><span class="na">threadLocalHashCode</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">INITIAL_CAPACITY</span> <span class="o">-</span> <span class="n">1</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">table</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="o">(</span><span class="n">firstKey</span><span class="o">,</span> <span class="n">firstValue</span><span class="o">);</span>   
</span></span><span class="line"><span class="cl">    <span class="c1">// 当前元素个数为1                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">size</span> <span class="o">=</span> <span class="n">1</span><span class="o">;</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">// 设置扩容阈值                                                    
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">setThreshold</span><span class="o">(</span><span class="n">INITIAL_CAPACITY</span><span class="o">);</span>                               
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 设置扩容阈值为 len 的 2/3
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">private</span> <span class="kt">void</span> <span class="nf">setThreshold</span><span class="o">(</span><span class="kt">int</span> <span class="n">len</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">threshold</span> <span class="o">=</span> <span class="n">len</span> <span class="o">*</span> <span class="n">2</span> <span class="o">/</span> <span class="n">3</span><span class="o">;</span>        
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="set">set<a hidden class="anchor" aria-hidden="true" href="#set">#</a></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span><span class="lnt">69
</span><span class="lnt">70
</span><span class="lnt">71
</span><span class="lnt">72
</span><span class="lnt">73
</span><span class="lnt">74
</span><span class="lnt">75
</span><span class="lnt">76
</span><span class="lnt">77
</span><span class="lnt">78
</span><span class="lnt">79
</span><span class="lnt">80
</span><span class="lnt">81
</span><span class="lnt">82
</span><span class="lnt">83
</span><span class="lnt">84
</span><span class="lnt">85
</span><span class="lnt">86
</span><span class="lnt">87
</span><span class="lnt">88
</span><span class="lnt">89
</span><span class="lnt">90
</span><span class="lnt">91
</span><span class="lnt">92
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 计算哈希表当前位置i的下一位置，一般情况为i + 1
</span></span></span><span class="line"><span class="cl"><span class="c1">// 但当超过数组长度len时，就重新回到数组开头位置0
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">nextIndex</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">,</span> <span class="kt">int</span> <span class="n">len</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="o">((</span><span class="n">i</span> <span class="o">+</span> <span class="n">1</span> <span class="o">&lt;</span> <span class="n">len</span><span class="o">)</span> <span class="o">?</span> <span class="n">i</span> <span class="o">+</span> <span class="n">1</span> <span class="o">:</span> <span class="n">0</span><span class="o">);</span>       
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 计算哈希表当前位置i的上一位置，一般情况为i - 1
</span></span></span><span class="line"><span class="cl"><span class="c1">// 但到达0位置时，它的上一位置是len - 1
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">private</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">prevIndex</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">,</span> <span class="kt">int</span> <span class="n">len</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="o">((</span><span class="n">i</span> <span class="o">-</span> <span class="n">1</span> <span class="o">&gt;=</span> <span class="n">0</span><span class="o">)</span> <span class="o">?</span> <span class="n">i</span> <span class="o">-</span> <span class="n">1</span> <span class="o">:</span> <span class="n">len</span> <span class="o">-</span> <span class="n">1</span><span class="o">);</span>  
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">set</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">key</span><span class="o">,</span> <span class="n">Object</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>                                                                          
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">tab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                                          
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">tab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>     
</span></span><span class="line"><span class="cl">    <span class="c1">// 计算参数key在哈希表中的对应的实际位置i                                   
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">key</span><span class="o">.</span><span class="na">threadLocalHashCode</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">len</span><span class="o">-</span><span class="n">1</span><span class="o">);</span>                    
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 线性探测法 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// 从i开始，往「后」遍历，直到i位置的Entry为null                                                              
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="o">(</span><span class="n">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>                                        
</span></span><span class="line"><span class="cl">         <span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>                                               
</span></span><span class="line"><span class="cl">         <span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">len</span><span class="o">)])</span> <span class="o">{</span>  
</span></span><span class="line"><span class="cl">        <span class="c1">// e.get()获取当前i位置的Key，是WeakReference中的方法，有可能返回null                      
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">k</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>                               
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// 找到当前key，表示是修改操作，直接修改value并返回                                                         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>                                           
</span></span><span class="line"><span class="cl">            <span class="n">e</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>                                      
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="o">;</span>                                               
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                                         
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="c1">// k为null的情况，表示该位置Entry的Key已经被回收，需要进行特殊处理，后面介绍                                                         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>                                          
</span></span><span class="line"><span class="cl">            <span class="n">replaceStaleEntry</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">,</span> <span class="n">i</span><span class="o">);</span>                     
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="o">;</span>                                               
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                                         
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                                             
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// i位置的Entry为null，表示该key还不存在，就把当前key、value放入i位置
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// 线性探测法，这里的i不一定为最初的由hashcode计算后的i                                                             
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>                               
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">sz</span> <span class="o">=</span> <span class="o">++</span><span class="n">size</span><span class="o">;</span>  
</span></span><span class="line"><span class="cl">    <span class="c1">// 先进行启发式清理操作，随后判断是否需要进行rehash操作                                            
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="o">(!</span><span class="n">cleanSomeSlots</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">sz</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">sz</span> <span class="o">&gt;=</span> <span class="n">threshold</span><span class="o">)</span>                
</span></span><span class="line"><span class="cl">        <span class="n">rehash</span><span class="o">();</span>                                                 
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">rehash</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// rehash前进行一个全面的清理                                     
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">expungeStaleEntries</span><span class="o">();</span>                                  
</span></span><span class="line"><span class="cl">                                                            
</span></span><span class="line"><span class="cl">    <span class="c1">// Use lower threshold for doubling to avoid hysteresis 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// 这里判断条件将扩容的要求缩减为了3/4的threshold，初始构造时threshold为2/3的len
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// 因此相当于扩容的要求为1/2的len 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="o">(</span><span class="n">size</span> <span class="o">&gt;=</span> <span class="n">threshold</span> <span class="o">-</span> <span class="n">threshold</span> <span class="o">/</span> <span class="n">4</span><span class="o">)</span>                  
</span></span><span class="line"><span class="cl">        <span class="n">resize</span><span class="o">();</span>                                           
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">resize</span><span class="o">()</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">// 新哈希表的容量为原来的2倍                                      
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">Entry</span><span class="o">[]</span> <span class="n">oldTab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                                   
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">oldLen</span> <span class="o">=</span> <span class="n">oldTab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>                               
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">newLen</span> <span class="o">=</span> <span class="n">oldLen</span> <span class="o">*</span> <span class="n">2</span><span class="o">;</span>                                  
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">newTab</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="o">[</span><span class="n">newLen</span><span class="o">];</span>                       
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="n">0</span><span class="o">;</span>                                            
</span></span><span class="line"><span class="cl">     
</span></span><span class="line"><span class="cl">    <span class="c1">// 将所有旧元素放到新的哈希表中                                                         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">0</span><span class="o">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">oldLen</span><span class="o">;</span> <span class="o">++</span><span class="n">j</span><span class="o">)</span> <span class="o">{</span>                        
</span></span><span class="line"><span class="cl">        <span class="n">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">oldTab</span><span class="o">[</span><span class="n">j</span><span class="o">];</span>                                  
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>                                      
</span></span><span class="line"><span class="cl">            <span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">k</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>                       
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// 旧元素中如果有已经成为stale entry的，直接将其value的引用断开
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="c1">// 方便GC回收value占用的空间                                  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="n">e</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> <span class="c1">// Help the GC                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">                <span class="c1">// 线性探测法将旧元素放到新表中的合适位置                                         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="kt">int</span> <span class="n">h</span> <span class="o">=</span> <span class="n">k</span><span class="o">.</span><span class="na">threadLocalHashCode</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">newLen</span> <span class="o">-</span> <span class="n">1</span><span class="o">);</span> 
</span></span><span class="line"><span class="cl">                <span class="k">while</span> <span class="o">(</span><span class="n">newTab</span><span class="o">[</span><span class="n">h</span><span class="o">]</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>                     
</span></span><span class="line"><span class="cl">                    <span class="n">h</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">h</span><span class="o">,</span> <span class="n">newLen</span><span class="o">);</span>                 
</span></span><span class="line"><span class="cl">                <span class="n">newTab</span><span class="o">[</span><span class="n">h</span><span class="o">]</span> <span class="o">=</span> <span class="n">e</span><span class="o">;</span>                                
</span></span><span class="line"><span class="cl">                <span class="n">count</span><span class="o">++;</span>                                      
</span></span><span class="line"><span class="cl">            <span class="o">}</span>                                                 
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                                     
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                                         
</span></span><span class="line"><span class="cl">   
</span></span><span class="line"><span class="cl">    <span class="c1">// 重新设置扩容阈值和当前元素个数，并将table指向新表，完成扩容操作                                                          
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">setThreshold</span><span class="o">(</span><span class="n">newLen</span><span class="o">);</span>                                     
</span></span><span class="line"><span class="cl">    <span class="n">size</span> <span class="o">=</span> <span class="n">count</span><span class="o">;</span>                                             
</span></span><span class="line"><span class="cl">    <span class="n">table</span> <span class="o">=</span> <span class="n">newTab</span><span class="o">;</span>                                           
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ol>
<li>
<p>首先介绍一下 <code>nextIndex</code> 和 <code>preIndex</code> 方法，它们分别计算当前位置 <code>i</code> 的下一个位置和上一个位置，这种计算方式使得数组的位置得到了循环利用，逻辑上构成了一个环形数组，<code>next</code> 表示顺时针，而 <code>pre</code> 表示逆时针，如下图所示：</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/EcMjfxBbpv8LrSt.png" title="https://i.loli.net/2021/09/25/EcMjfxBbpv8LrSt.png" data-thumbnail="https://i.loli.net/2021/09/25/EcMjfxBbpv8LrSt.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/EcMjfxBbpv8LrSt.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/EcMjfxBbpv8LrSt.png"
            alt="https://i.loli.net/2021/09/25/EcMjfxBbpv8LrSt.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
<li>
<p><code>set</code> 方法的主要作用是新增和修改哈希表中的元素，处理冲突的方式也是常用的线性探测法，即如果使用 Key（<code>ThreadLocal</code> 类型）的 <code>threadLocalHashCode</code> 计算出的位置已经存在 <code>Entry</code>（这个 <code>Entry</code> 有可能是有效的元素，也有可能是 Key 已经被回收的 <code>stale entry</code>），就进入循环，判断是否是修改操作。注意循环中还有个 <code>replaceStaleEntry</code>，它会执行一些清理工作，然后将 <code>key</code>、<code>value</code> 放到合适的 <code>Entry</code> 中，后面会详细介绍。一直探测到某个位置的 <code>Entry</code> 为 <code>null</code>，就用 <code>key</code> 、<code>value</code> 新建 <code>Entry</code> 并放在该位置。</p>
</li>
<li>
<p><code>rehash</code> 操作前，会先进行一次 <code>cleanSomeSlots</code> 清理操作，这个方法在源码注释中使用了 <em>Heuristically（启发式地）</em> 进行描述，因此这里简称它为 <code>启发式清理</code>。而在 <code>rehash</code> 方法中，在调用 <code>resize</code> 方法扩容前，还会调用另外一个 <code>expungeStaleEntries</code> 清理操作，熟悉的词汇，在源码注释中描述为 <em>Expunge all stale entries in the table（清理所有 stale entry）</em>，它本质上是调用了 <code>expungeStaleEntry</code> 方法，而 <code>expungeStaleEntry</code> 方法是对哈希表中的 stale entry 进行部分清理，后面就简称它为 <code>分段式清理</code>。</p>
</li>
<li>
<p>两个清理工作完成后，才开始正式的 <code>resize</code> 扩容流程，新建一个两倍容量的数组，将旧表中的元素转移到新表，同时清理一些 stale entry。</p>
</li>
</ol>
<h3 id="getentry">getEntry<a hidden class="anchor" aria-hidden="true" href="#getentry">#</a></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">Entry</span> <span class="nf">getEntry</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>  
</span></span><span class="line"><span class="cl">     <span class="c1">// 计算key对应在哈希表中的实际位置i，作为查找的起点           
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>     <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">key</span><span class="o">.</span><span class="na">threadLocalHashCode</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">table</span><span class="o">.</span><span class="na">length</span> <span class="o">-</span> <span class="n">1</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">     <span class="n">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">table</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>
</span></span><span class="line"><span class="cl">     <span class="c1">// 如果i位置的entry不为空，且直接就是要找的key，直接返回，提高效率                                  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>     <span class="k">if</span> <span class="o">(</span><span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">()</span> <span class="o">==</span> <span class="n">key</span><span class="o">)</span>                     
</span></span><span class="line"><span class="cl">         <span class="k">return</span> <span class="n">e</span><span class="o">;</span>                                        
</span></span><span class="line"><span class="cl">     <span class="k">else</span>
</span></span><span class="line"><span class="cl">         <span class="c1">// 否则，需要进一步查询                                                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>         <span class="k">return</span> <span class="n">getEntryAfterMiss</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">i</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>             
</span></span><span class="line"><span class="cl"> <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">Entry</span> <span class="nf">getEntryAfterMiss</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">key</span><span class="o">,</span> <span class="kt">int</span> <span class="n">i</span><span class="o">,</span> <span class="n">Entry</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">tab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                                             
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">tab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>                                            
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// 当前entry不为空，可能是有效entry，也可能是stale entry                                                                  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">while</span> <span class="o">(</span><span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>                                              
</span></span><span class="line"><span class="cl">        <span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">k</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 找到了目标key，直接返回该Entry                                  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="n">key</span><span class="o">)</span>                                                
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">e</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// k为null，表示该Entry是stale entry，以i为起点进行分段清理                                                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span>                                               
</span></span><span class="line"><span class="cl">            <span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>                                    
</span></span><span class="line"><span class="cl">        <span class="k">else</span> 
</span></span><span class="line"><span class="cl">            <span class="c1">// 表示当前位置是有效entry，但不是目标entry，继续查找下一个位置                                                        
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>                                   
</span></span><span class="line"><span class="cl">        <span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>                                                  
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// entry数组中的查找碰到null，表示查找失败，哈希表中不存在该key，返回null                                                                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="kc">null</span><span class="o">;</span>                                                     
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>getEntry</code> 的流程整体上比较简单，和普通线性探测哈希表的 get 方法没什么区别：</p>
<ol>
<li>使用 key 的 <code>threadLocalHashCode</code> 计算出实际位置 <code>i</code>，以这个 <code>i</code> 为查找的起点，如果 <code>i</code> 位置的 Entry 就是我们想要查找的目标（<code>e.get() == key</code>），则直接返回。其实这里 <code>e == null</code> 时也可以直接返回 <code>null</code>，不过代码中把它延迟到了 <code>getEntryAfterMiss</code> 中，没什么区别。</li>
<li><code>getEntryAfterMiss</code> 就从起点 <code>i</code> 开始，向后查找（<code>nextIndex</code>），如果找到目标，直接返回 Entry，如果遇到 <code>null</code>，直接返回 <code>null</code> 表示哈希表中没有该目标，这两个操作与普通线性探测法一致。不同的是当遇到 <code>k == null</code>，也就是 Entry 为 stale entry 时，需要多进行一次 <code>分段式清理</code> 操作。</li>
</ol>
<h3 id="remove">remove<a hidden class="anchor" aria-hidden="true" href="#remove">#</a></h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">remove</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>      
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">tab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                       
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">tab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>                      
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">key</span><span class="o">.</span><span class="na">threadLocalHashCode</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">len</span><span class="o">-</span><span class="n">1</span><span class="o">);</span> 
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="o">(</span><span class="n">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>                     
</span></span><span class="line"><span class="cl">         <span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>                            
</span></span><span class="line"><span class="cl">         <span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">len</span><span class="o">)])</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// 找到目标     
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">()</span> <span class="o">==</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 断开key的弱引用                  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">e</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 以i为起点进行一次分段清理                         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>              
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="o">;</span>                            
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                      
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                          
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>线性探测法的 <code>remove</code> 操作其实是比较繁琐的，上面的代码看上去很简单，因为它把具体的操作放到了 <code>分段式清理</code> 的方法中，接下来就是要对清理方法进行分析。</p>
<h3 id="清理">清理<a hidden class="anchor" aria-hidden="true" href="#清理">#</a></h3>
<p>从上面对几个增删改查操作的源码，不难发现，大多数方法除了完成自身的本职工作外，都会附带地在某些条件下对哈希表进行一些清理工作，包括 <code>分段式清理</code> 和 <code>启发式清理</code>，下面将分别进行分析。</p>
<ul>
<li>分段式清理</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">int</span> <span class="nf">expungeStaleEntry</span><span class="o">(</span><span class="kt">int</span> <span class="n">staleSlot</span><span class="o">)</span> <span class="o">{</span>                            
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">tab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                                                  
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">tab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>                                                 
</span></span><span class="line"><span class="cl">                                                                          
</span></span><span class="line"><span class="cl">    <span class="c1">// expunge entry at staleSlot 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// 这步很简单，就是简单的删除staleSlot位置的entry
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// 断开entry中指向value的强引用，以便value会被GC回收                                      
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">].</span><span class="na">value</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>  
</span></span><span class="line"><span class="cl">    <span class="c1">// 清空数组当前位置                                       
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">]</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>                                                
</span></span><span class="line"><span class="cl">    <span class="n">size</span><span class="o">--;</span>                                                               
</span></span><span class="line"><span class="cl">                                                                          
</span></span><span class="line"><span class="cl">    <span class="c1">// Rehash until we encounter null                                     
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">Entry</span> <span class="n">e</span><span class="o">;</span>                                                              
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">i</span><span class="o">;</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">// 从被删除元素的下个位置开始，对每个Entry进行rehash操作，直到键簇的末尾(遇到null)                                                              
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="o">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">staleSlot</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>                                   
</span></span><span class="line"><span class="cl">         <span class="o">(</span><span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">])</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>                                            
</span></span><span class="line"><span class="cl">         <span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">len</span><span class="o">))</span> <span class="o">{</span>                                         
</span></span><span class="line"><span class="cl">        <span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">k</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">();</span> 
</span></span><span class="line"><span class="cl">        <span class="c1">// 比起普通线性探测的删除，多了这个清理stale entry的操作
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// k == null，表示当前entry为stale entry                                      
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// 同样，断开value的强引用，将table                                                  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">e</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span> 
</span></span><span class="line"><span class="cl">            <span class="c1">// 清空数组当前位置                                              
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>                                                
</span></span><span class="line"><span class="cl">            <span class="n">size</span><span class="o">--;</span>                                                       
</span></span><span class="line"><span class="cl">        <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// key不为空表示该entry有效，则进行rehash操作
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="c1">// 重新计算位置                                                          
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="kt">int</span> <span class="n">h</span> <span class="o">=</span> <span class="n">k</span><span class="o">.</span><span class="na">threadLocalHashCode</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">len</span> <span class="o">-</span> <span class="n">1</span><span class="o">);</span>  
</span></span><span class="line"><span class="cl">            <span class="c1">// 新位置h与当前位置i不相等，表示它是因为哈希冲突被「挤」到i位置
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="c1">// rehash后它有机会更靠近h位置                  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">if</span> <span class="o">(</span><span class="n">h</span> <span class="o">!=</span> <span class="n">i</span><span class="o">)</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">                <span class="c1">// 这个操作很重要，表示将当前i位置留空，
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="c1">// 保证rehash后，当前entry至少能再次放到这个i位置                                                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>                                            
</span></span><span class="line"><span class="cl">                                                                          
</span></span><span class="line"><span class="cl">                <span class="c1">// Unlike Knuth 6.4 Algorithm R, we must scan until       
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="c1">// null because multiple entries could have been stale.  
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="c1">// 从h位置往后找到第一个为null的位置即为该entry的新位置
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="c1">// 上面在i位置留了个空，因此最坏情况是最终h==i
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">while</span> <span class="o">(</span><span class="n">tab</span><span class="o">[</span><span class="n">h</span><span class="o">]</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>                                    
</span></span><span class="line"><span class="cl">                    <span class="n">h</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">h</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>                                
</span></span><span class="line"><span class="cl">                <span class="n">tab</span><span class="o">[</span><span class="n">h</span><span class="o">]</span> <span class="o">=</span> <span class="n">e</span><span class="o">;</span>                                               
</span></span><span class="line"><span class="cl">            <span class="o">}</span>                                                             
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                                                 
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                                                     
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">i</span><span class="o">;</span>                                                             
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>这个清理基本上等同于普通线性探测法的删除操作，只是在 rehash 的过程中增加了一个删除 stale entry 的步骤。下面以一个示例对流程进行讲解：</p>
<ol>
<li>
<p>初始状态：<code>K1~K7</code> 代表一个键簇，假定 <code>K1~K7</code> 计算后得到的位置均为 <code>13</code>。图中绿色表示有效 entry，灰色表示 stale entry，而白色为 <code>null</code>。现在开始执行 <code>expungeStaleEntry(13)</code>，即传入的参数 <code>staleSlot = 13</code>。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/Vo8XRde32W6vCAn.png" title="https://i.loli.net/2021/09/25/Vo8XRde32W6vCAn.png" data-thumbnail="https://i.loli.net/2021/09/25/Vo8XRde32W6vCAn.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/Vo8XRde32W6vCAn.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/Vo8XRde32W6vCAn.png"
            alt="https://i.loli.net/2021/09/25/Vo8XRde32W6vCAn.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
<li>
<p>根据步骤，首先删除 <code>K1</code> 的 <code>Entry</code>，并将 <code>i</code> 移动到 <code>K1</code> 的下个位置 <code>14</code>：</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/UbnYR2vdl7FVTZ3.png" title="https://i.loli.net/2021/09/25/UbnYR2vdl7FVTZ3.png" data-thumbnail="https://i.loli.net/2021/09/25/UbnYR2vdl7FVTZ3.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/UbnYR2vdl7FVTZ3.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/UbnYR2vdl7FVTZ3.png"
            alt="https://i.loli.net/2021/09/25/UbnYR2vdl7FVTZ3.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
<li>
<p>随后，<code>K2</code> 位置为 stale entry，进入 <code>k == null</code> 分支，删除 <code>K2</code>，进入下次循环，<code>i</code> 到达 <code>15</code>，<code>K3</code> 为有效 entry，进行 rehash 操作，将 <code>h</code> 进行计算 <code>h = 13</code>（1 中的假设）。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/Yxi7F5KkSvDQjaL.png" title="https://i.loli.net/2021/09/25/Yxi7F5KkSvDQjaL.png" data-thumbnail="https://i.loli.net/2021/09/25/Yxi7F5KkSvDQjaL.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/Yxi7F5KkSvDQjaL.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/Yxi7F5KkSvDQjaL.png"
            alt="https://i.loli.net/2021/09/25/Yxi7F5KkSvDQjaL.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
<li>
<p>先清空 <code>i</code> 位置，随后开始判断 <code>h</code> 位置，刚好 <code>h</code> 位置为空，则直接将 <code>K3</code> 代表的 <code>Entry</code> 放入 <code>13</code> 位置，<code>i</code> 移动到 <code>0</code> 位置。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/7lcorFteNGsDApC.png" title="https://i.loli.net/2021/09/25/7lcorFteNGsDApC.png" data-thumbnail="https://i.loli.net/2021/09/25/7lcorFteNGsDApC.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/7lcorFteNGsDApC.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/7lcorFteNGsDApC.png"
            alt="https://i.loli.net/2021/09/25/7lcorFteNGsDApC.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
<li>
<p>与步骤 3 类似，清空 <code>K4</code>，<code>i</code> 移动至 <code>1</code> 位置。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/SveMP48UqOtGFBc.png" title="https://i.loli.net/2021/09/25/SveMP48UqOtGFBc.png" data-thumbnail="https://i.loli.net/2021/09/25/SveMP48UqOtGFBc.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/SveMP48UqOtGFBc.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/SveMP48UqOtGFBc.png"
            alt="https://i.loli.net/2021/09/25/SveMP48UqOtGFBc.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
<li>
<p><code>K5~K7</code> 均为有效 entry，因此进行 rehash 操作，<code>K5</code> 的 <code>h = 13</code>，此时 <code>13</code> 位置不为空，则 <code>h</code> 移动到 <code>14</code>，<code>14</code> 位置为空，则将 <code>K5</code> 的 <code>Entry</code> 移动到 <code>14</code>。同理，将 <code>K6</code> 和 <code>K7</code> 移动到 <code>15</code> 和 <code>0</code> 位置。最后，<code>i</code> 移动到 <code>4</code> 的位置（<strong>原</strong> 键簇末尾紧邻的 null 位置），返回 <code>i</code>（马上会用到），本次 <code>分段式清理</code> 结束。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/bvNpCDqBdocyRhi.png" title="https://i.loli.net/2021/09/25/bvNpCDqBdocyRhi.png" data-thumbnail="https://i.loli.net/2021/09/25/bvNpCDqBdocyRhi.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/bvNpCDqBdocyRhi.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/bvNpCDqBdocyRhi.png"
            alt="https://i.loli.net/2021/09/25/bvNpCDqBdocyRhi.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
</ol>
<p>了解过 <code>expungeStaleEntry</code> 基本原理后，回头看看 <code>rehash</code> 代码中调用的 <code>expungeStaleEntries</code> 方法：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**                                       
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Expunge all stale entries in the table.
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>                                       
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">expungeStaleEntries</span><span class="o">()</span> <span class="o">{</span>      
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">tab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                  
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">tab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>   
</span></span><span class="line"><span class="cl">    <span class="c1">// 遍历哈希表每个位置，对stale entry进行清理              
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="n">0</span><span class="o">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">len</span><span class="o">;</span> <span class="n">j</span><span class="o">++)</span> <span class="o">{</span>       
</span></span><span class="line"><span class="cl">        <span class="n">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">j</span><span class="o">];</span>                 
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> 
</span></span><span class="line"><span class="cl">            <span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">j</span><span class="o">);</span>         
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                     
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>是不是就毫无难度了，这就是一个简单粗暴的全局大清理工作。</p>
<ul>
<li>启发式清理</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**                                                            
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Heuristically scan some cells looking for stale entries.    
</span></span></span><span class="line"><span class="cl"><span class="cm"> * This is invoked when either a new element is added, or      
</span></span></span><span class="line"><span class="cl"><span class="cm"> * another stale one has been expunged. It performs a          
</span></span></span><span class="line"><span class="cl"><span class="cm"> * logarithmic number of scans, as a balance between no        
</span></span></span><span class="line"><span class="cl"><span class="cm"> * scanning (fast but retains garbage) and a number of scans   
</span></span></span><span class="line"><span class="cl"><span class="cm"> * proportional to number of elements, that would find all     
</span></span></span><span class="line"><span class="cl"><span class="cm"> * garbage but would cause some insertions to take O(n) time.  
</span></span></span><span class="line"><span class="cl"><span class="cm"> *                                                             
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param i a position known NOT to hold a stale entry. The    
</span></span></span><span class="line"><span class="cl"><span class="cm"> * scan starts at the element after i.                         
</span></span></span><span class="line"><span class="cl"><span class="cm"> *                                                             
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param n scan control: {@code log2(n)} cells are scanned,   
</span></span></span><span class="line"><span class="cl"><span class="cm"> * unless a stale entry is found, in which case                
</span></span></span><span class="line"><span class="cl"><span class="cm"> * {@code log2(table.length)-1} additional cells are scanned.  
</span></span></span><span class="line"><span class="cl"><span class="cm"> * When called from insertions, this parameter is the number   
</span></span></span><span class="line"><span class="cl"><span class="cm"> * of elements, but when from replaceStaleEntry, it is the     
</span></span></span><span class="line"><span class="cl"><span class="cm"> * table length. (Note: all this could be changed to be either 
</span></span></span><span class="line"><span class="cl"><span class="cm"> * more or less aggressive by weighting n instead of just      
</span></span></span><span class="line"><span class="cl"><span class="cm"> * using straight log n. But this version is simple, fast, and 
</span></span></span><span class="line"><span class="cl"><span class="cm"> * seems to work well.)                                        
</span></span></span><span class="line"><span class="cl"><span class="cm"> *                                                             
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @return true if any stale entries have been removed.        
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">cleanSomeSlots</span><span class="o">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">,</span> <span class="kt">int</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span> 
</span></span><span class="line"><span class="cl">    <span class="kt">boolean</span> <span class="n">removed</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>                   
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">tab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                       
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">tab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>                      
</span></span><span class="line"><span class="cl">    <span class="k">do</span> <span class="o">{</span>                                       
</span></span><span class="line"><span class="cl">        <span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>                 
</span></span><span class="line"><span class="cl">        <span class="n">Entry</span> <span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">];</span>                      
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="n">e</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>    
</span></span><span class="line"><span class="cl">            <span class="n">n</span> <span class="o">=</span> <span class="n">len</span><span class="o">;</span>                           
</span></span><span class="line"><span class="cl">            <span class="n">removed</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>                    
</span></span><span class="line"><span class="cl">            <span class="n">i</span> <span class="o">=</span> <span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>          
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                      
</span></span><span class="line"><span class="cl">    <span class="o">}</span> <span class="k">while</span> <span class="o">(</span> <span class="o">(</span><span class="n">n</span> <span class="o">&gt;&gt;&gt;=</span> <span class="n">1</span><span class="o">)</span> <span class="o">!=</span> <span class="n">0</span><span class="o">);</span>                
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">removed</span><span class="o">;</span>                            
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>这里把源码中的所有注释都搬进来了，非常详细的一段注释，从设计思想到各参数的详细讲解，应有尽有。代码不长，核心循环的工作是以 <code>i</code> 为起点对哈希表进行扫描（注释中重点写明这个起始 <code>i</code> 位置一定 <strong>不是</strong> stale entry），判断是否存在 stale entry。如果一直没扫描到，那么在扫描 $log_2 n$ 次后就结束循环，返回 <code>false</code>。如果扫描到存在 stale entry，那么 <code>cleanSomeSlots</code> 调用我们刚介绍过的 <code>expungeStaleEntry</code> 进行清理，<code>i</code> 的值将直接跳到被清理键簇的紧邻 <code>null</code> 位置，并且会将扫描次数扩大，进行额外的 $log_2 (table.length)-1$ 次扫描。</p>
<p>每次发现 stale entry，就会重新将扫描次数进行增加，哈希表中的 stale entry 越多，扫描的次数就会越多，进行的清理操作就越多，这就是一个逐步启发的过程。代码注释中说到这种方式是一种折中的实现，在完全不进行扫描和全局扫描之间找到一个平衡点。</p>
<p>这个方法会在两个地方被调用，第一个是在 <code>set</code> 方法的末尾，新增元素成功后，在 <code>rehash</code> 之前进行一次启发式清理，这时候传入的两个参数分别为新增元素的位置 <code>i</code> 及新增后所有元素的个数 <code>sz</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// 在i位置新增entry元素                                                            
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>                               
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">sz</span> <span class="o">=</span> <span class="o">++</span><span class="n">size</span><span class="o">;</span>  
</span></span><span class="line"><span class="cl"><span class="c1">// 先进行启发式清理操作，随后判断是否需要进行rehash操作                                            
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="o">(!</span><span class="n">cleanSomeSlots</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">sz</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">sz</span> <span class="o">&gt;=</span> <span class="n">threshold</span><span class="o">)</span>                
</span></span><span class="line"><span class="cl">    <span class="n">rehash</span><span class="o">();</span>  
</span></span></code></pre></td></tr></table>
</div>
</div><p>第二个被调用的地方就是我们之前一笔带过的 <code>replaceStaleEntry</code>，这个方法逻辑比较复杂，涉及的内容比较多，因此我放到了最后再来补上。</p>
<ul>
<li>replaceStaleEntry</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">void</span> <span class="nf">replaceStaleEntry</span><span class="o">(</span><span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">key</span><span class="o">,</span> <span class="n">Object</span> <span class="n">value</span><span class="o">,</span>           
</span></span><span class="line"><span class="cl">                               <span class="kt">int</span> <span class="n">staleSlot</span><span class="o">)</span> <span class="o">{</span>                            
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span><span class="o">[]</span> <span class="n">tab</span> <span class="o">=</span> <span class="n">table</span><span class="o">;</span>                                                   
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">tab</span><span class="o">.</span><span class="na">length</span><span class="o">;</span>                                                  
</span></span><span class="line"><span class="cl">    <span class="n">Entry</span> <span class="n">e</span><span class="o">;</span>                                                               
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">    <span class="c1">// Back up to check for prior stale entry in current run.              
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// We clean out whole runs at a time to avoid continual                
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// incremental rehashing due to garbage collector freeing              
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// up refs in bunches (i.e., whenever the collector runs).             
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kt">int</span> <span class="n">slotToExpunge</span> <span class="o">=</span> <span class="n">staleSlot</span><span class="o">;</span>                                         
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">prevIndex</span><span class="o">(</span><span class="n">staleSlot</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>                                
</span></span><span class="line"><span class="cl">         <span class="o">(</span><span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">])</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>                                             
</span></span><span class="line"><span class="cl">         <span class="n">i</span> <span class="o">=</span> <span class="n">prevIndex</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">len</span><span class="o">))</span>                                            
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span>                                               
</span></span><span class="line"><span class="cl">            <span class="n">slotToExpunge</span> <span class="o">=</span> <span class="n">i</span><span class="o">;</span>                                             
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">    <span class="c1">// Find either the key or trailing null slot of run, whichever         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// occurs first                                                        
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">staleSlot</span><span class="o">,</span> <span class="n">len</span><span class="o">);</span>                                
</span></span><span class="line"><span class="cl">         <span class="o">(</span><span class="n">e</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">])</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>                                             
</span></span><span class="line"><span class="cl">         <span class="n">i</span> <span class="o">=</span> <span class="n">nextIndex</span><span class="o">(</span><span class="n">i</span><span class="o">,</span> <span class="n">len</span><span class="o">))</span> <span class="o">{</span>                                          
</span></span><span class="line"><span class="cl">        <span class="n">ThreadLocal</span><span class="o">&lt;?&gt;</span> <span class="n">k</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>                                        
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">        <span class="c1">// If we find key, then we need to swap it                         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// with the stale entry to maintain hash table order.              
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// The newly stale slot, or any other stale slot                   
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// encountered above it, can then be sent to expungeStaleEntry     
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// to remove or rehash all of the other entries in run.            
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>                                                    
</span></span><span class="line"><span class="cl">            <span class="n">e</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>                                               
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">            <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">];</span>                                       
</span></span><span class="line"><span class="cl">            <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">]</span> <span class="o">=</span> <span class="n">e</span><span class="o">;</span>                                            
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">            <span class="c1">// Start expunge at preceding stale entry if it exists         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="k">if</span> <span class="o">(</span><span class="n">slotToExpunge</span> <span class="o">==</span> <span class="n">staleSlot</span><span class="o">)</span>                                
</span></span><span class="line"><span class="cl">                <span class="n">slotToExpunge</span> <span class="o">=</span> <span class="n">i</span><span class="o">;</span>                                         
</span></span><span class="line"><span class="cl">            <span class="n">cleanSomeSlots</span><span class="o">(</span><span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">slotToExpunge</span><span class="o">),</span> <span class="n">len</span><span class="o">);</span>         
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="o">;</span>                                                        
</span></span><span class="line"><span class="cl">        <span class="o">}</span>                                                                  
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">        <span class="c1">// If we didn&#39;t find stale entry on backward scan, the             
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// first stale entry seen while scanning for key is the            
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="c1">// first still present in the run.                                 
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">slotToExpunge</span> <span class="o">==</span> <span class="n">staleSlot</span><span class="o">)</span>                       
</span></span><span class="line"><span class="cl">            <span class="n">slotToExpunge</span> <span class="o">=</span> <span class="n">i</span><span class="o">;</span>                                             
</span></span><span class="line"><span class="cl">    <span class="o">}</span>                                                                      
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">    <span class="c1">// If key not found, put new entry in stale slot                       
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">].</span><span class="na">value</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>                                           
</span></span><span class="line"><span class="cl">    <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>                                
</span></span><span class="line"><span class="cl">                                                                           
</span></span><span class="line"><span class="cl">    <span class="c1">// If there are any other stale entries in run, expunge them           
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="o">(</span><span class="n">slotToExpunge</span> <span class="o">!=</span> <span class="n">staleSlot</span><span class="o">)</span>                                        
</span></span><span class="line"><span class="cl">        <span class="n">cleanSomeSlots</span><span class="o">(</span><span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">slotToExpunge</span><span class="o">),</span> <span class="n">len</span><span class="o">);</span>             
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>这也是个非常繁琐的方法，但是注释内容较多，理解起来也很方便。</p>
<ol>
<li>
<p>这个方法是在 <code>set</code> 中被调用的，在线性探测插入（或修改）元素时，如果遇到了 stale entry，那么就进入到 <code>replaceStaleEntry</code>，传入的参数为元素的 <code>key</code>、<code>value</code> 以及 stale entry 的位置 <code>i</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// k为null的情况，表示stale entry                                                        
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>                                          
</span></span><span class="line"><span class="cl">    <span class="n">replaceStaleEntry</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">,</span> <span class="n">i</span><span class="o">);</span>                     
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="o">;</span>                                               
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><code>replaceStaleEntry</code> 中的第一个循环主要作用是找到 <code>i</code> 位置所在键簇最前端的某个 stale entry 位置。举例说明，<code>set</code> 方法将传入参数 <code>K8</code>，图中 <code>K8</code> 为待探测元素，计算得到它的起始位置为 <code>0</code>。由于 <code>K4</code> 为有效 entry，且 <code>K4 ≠ K8</code>，因此 <code>set</code> 方法中的 <code>i</code> 移动至 <code>1</code> 位置。<code>1</code> 位置上的 <code>K5</code> 是 stale entry，因此，从这里开始调用 <code>replaceStaleEntry</code>，传入的第三个参数 <code>staleSlot</code> 为 <code>1</code>。这时候，<code>replaceStaleEntry</code> 的第一个循环就从这个 <code>staleSlot</code> 开始 <strong>向前移动</strong>，寻找最前端的 stale slot，即 <code>13</code>（虽然 <code>15</code> 也是 stale slot，但它不是这个键簇的最前端），并赋值 <code>slotToExpunge = 13</code>。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/rmgx6PUztFLnW8R.png" title="https://i.loli.net/2021/09/25/rmgx6PUztFLnW8R.png" data-thumbnail="https://i.loli.net/2021/09/25/rmgx6PUztFLnW8R.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/rmgx6PUztFLnW8R.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/rmgx6PUztFLnW8R.png"
            alt="https://i.loli.net/2021/09/25/rmgx6PUztFLnW8R.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
</li>
<li>
<p>第二个循环从 <code>staleSlot</code> 的下个位置开始，<strong>往后移动</strong>，在键簇中寻找 <code>k == key</code> 的 <code>Entry</code>，直到键簇末尾。注意循环末尾的一小段代码：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">slotToExpunge</span> <span class="o">==</span> <span class="n">staleSlot</span><span class="o">)</span>                       
</span></span><span class="line"><span class="cl">    <span class="n">slotToExpunge</span> <span class="o">=</span> <span class="n">i</span><span class="o">;</span> 
</span></span></code></pre></td></tr></table>
</div>
</div><p>它表示如果在 <strong>往后</strong>（区别步骤 2 中的往前）寻找的过程中遇到了 stale entry，且刚才步骤 2 中没找到 stale entry，那么就将 <code>slotToExpunge</code> 赋值为这个 stale entry 的位置 <code>i</code>。再用一个例子来说明，如下图所示，同样从 <code>set</code> <code>K8</code> 元素开始，到 <code>1</code> 位置进入 <code>replaceStaleEntry</code>，此时往前寻找不到 stale entry，那么进入第二个循环前，<code>slotToExpunge == staleSlot</code>。</p>
<p>进入第二个循环后，向后寻找到 <code>2</code> 位置，发现 <code>K6</code> 是 stale slot，即 <code>k == null</code>，且这时候满足第二个条件，因此 <code>slotToExpunge = 2</code>。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/YftE2wO5p4bFIR8.png" title="https://i.loli.net/2021/09/25/YftE2wO5p4bFIR8.png" data-thumbnail="https://i.loli.net/2021/09/25/YftE2wO5p4bFIR8.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/YftE2wO5p4bFIR8.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/YftE2wO5p4bFIR8.png"
            alt="https://i.loli.net/2021/09/25/YftE2wO5p4bFIR8.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>这个赋值操作最多只会执行一次，第二次再进来 <code>slotToExpunge == staleSlot</code> 这个条件一定不会再满足了，这个循环的起始位置是 <code>staleSlot</code> 的 <strong>下个位置</strong>，已经就不等于 <code>staleSlot</code> 了，往后的 <code>i</code> 值就更不会满足该条件。</p>
</li>
<li>
<p>第二个循环过程中，如果找到了满足 <code>k == key</code> 条件的 <code>Entry</code>，那么就会进入替换及清理的代码中：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">(</span><span class="n">k</span> <span class="o">==</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span>                                                    
</span></span><span class="line"><span class="cl">    <span class="n">e</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>                                               
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">tab</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">];</span>                                       
</span></span><span class="line"><span class="cl">    <span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">]</span> <span class="o">=</span> <span class="n">e</span><span class="o">;</span>                                            
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Start expunge at preceding stale entry if it exists         
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span> <span class="o">(</span><span class="n">slotToExpunge</span> <span class="o">==</span> <span class="n">staleSlot</span><span class="o">)</span>                                
</span></span><span class="line"><span class="cl">        <span class="n">slotToExpunge</span> <span class="o">=</span> <span class="n">i</span><span class="o">;</span>                                         
</span></span><span class="line"><span class="cl">    <span class="n">cleanSomeSlots</span><span class="o">(</span><span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">slotToExpunge</span><span class="o">),</span> <span class="n">len</span><span class="o">);</span>         
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="o">;</span>                                                        
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>staleSlot</code> 是调用 <code>replaceStaleEntry</code> 方法时传入的参数，也就是 <code>set</code> 方法调用过程中发现的第一个 stale entry 的位置。这里先将当前 <code>Entry</code> 的 <code>value</code> 进行了替换修改，然后将当前位置 <code>i</code> 与 <code>staleSlot</code> 位置的元素进行了交换，交换过后，<code>i</code> 位置变为 stale entry，而 <code>staleSlot</code> 位置成为了有效 entry。</p>
<p>这段代码就是 <code>replaceStaleEntry</code> 命名的由来，它将原来 <code>set</code> 中识别出的 stale entry 替换为了一个新的有效 entry（key 是原来已经存在的，仅修改了 value）。下图中，<code>K8 == K8'</code>，当 <code>i == 4</code> 时，进入上述逻辑中，先将 <code>K8'</code> 的 <code>value</code> 进行替换修改，再将 <code>K5</code> 与 <code>K8'</code> 进行交换，得到下面的成果。</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/RnS1LPQ6hMsr3oZ.png" title="https://i.loli.net/2021/09/25/RnS1LPQ6hMsr3oZ.png" data-thumbnail="https://i.loli.net/2021/09/25/RnS1LPQ6hMsr3oZ.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/RnS1LPQ6hMsr3oZ.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/RnS1LPQ6hMsr3oZ.png"
            alt="https://i.loli.net/2021/09/25/RnS1LPQ6hMsr3oZ.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/yRc5FGDdluv3hE8.png" title="https://i.loli.net/2021/09/25/yRc5FGDdluv3hE8.png" data-thumbnail="https://i.loli.net/2021/09/25/yRc5FGDdluv3hE8.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/yRc5FGDdluv3hE8.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/yRc5FGDdluv3hE8.png"
            alt="https://i.loli.net/2021/09/25/yRc5FGDdluv3hE8.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>替换成功后，随后条件判断与步骤 3 逻辑相同，都是确定 <code>slotToExpunge</code> 的位置，此时的 <code>i</code> 位置已经是 stale entry 了，因此可以作为 <code>expungeStaleEntry</code> <code>分段式清理</code> 的起点。</p>
<p>最后就是进行两次清理，先分段清理，再将其返回值传入 <code>cleanSomeSlots</code> 进行启发式清理，启发式清理中的第二个参数为 <code>len</code>，即哈希表当前的最大容量，区别 <code>set</code> 方法末尾的参数传入的 <code>sz</code>。</p>
</li>
<li>
<p>若第二个循环中没有找到能够替换的 <code>Entry</code>，则进入到最后的新建逻辑：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"> <span class="c1">// If key not found, put new entry in stale slot                       
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">].</span><span class="na">value</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>                                           
</span></span><span class="line"><span class="cl"><span class="n">tab</span><span class="o">[</span><span class="n">staleSlot</span><span class="o">]</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Entry</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>                                
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// If there are any other stale entries in run, expunge them           
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="o">(</span><span class="n">slotToExpunge</span> <span class="o">!=</span> <span class="n">staleSlot</span><span class="o">)</span>                                        
</span></span><span class="line"><span class="cl">    <span class="n">cleanSomeSlots</span><span class="o">(</span><span class="n">expungeStaleEntry</span><span class="o">(</span><span class="n">slotToExpunge</span><span class="o">),</span> <span class="n">len</span><span class="o">);</span> 
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>staleSlot</code> 处成为新元素插入的位置，如果在第二个循环中发现了其他 stale entry，就进行两步清理工作。</p>
</li>
</ol>
<hr>
<h2 id="threadlocal-注意事项">ThreadLocal 注意事项<a hidden class="anchor" aria-hidden="true" href="#threadlocal-注意事项">#</a></h2>
<h3 id="内存泄漏">内存泄漏<a hidden class="anchor" aria-hidden="true" href="#内存泄漏">#</a></h3>
<p><figure><a class="lightgallery" href="https://i.loli.net/2021/09/25/td1sG2VzF9WQwTN.png" title="https://i.loli.net/2021/09/25/td1sG2VzF9WQwTN.png" data-thumbnail="https://i.loli.net/2021/09/25/td1sG2VzF9WQwTN.png">
        <img
            class="lazy"
            src="/svg/loading.min.svg"
            data-src="https://i.loli.net/2021/09/25/td1sG2VzF9WQwTN.png"
            loading="lazy"
            title="https://i.loli.net/2021/09/25/td1sG2VzF9WQwTN.png"
            alt="https://i.loli.net/2021/09/25/td1sG2VzF9WQwTN.png" />
    </a><figcaption class="image-caption"></figcaption>
</figure>
</p>
<p>根据官方文档的推荐，我们平时使用 <code>ThreadLocal</code> 往往都会将它声名为 <code>private static</code>，那么，上图中红色部分的强引用将会一直存在（metaspace 中），该 <code>ThreadLocal</code> 在一个长期执行线程的 <code>Thread.threadLocals</code> 哈希表中对应的一个 <code>Entry e</code>，由于强引用的存在，<code>e.get()</code> 返回的 <strong>不会</strong> 是 <code>null</code>，那么指望上面的各种自动清理方法回收 <code>value</code> 内存就不太现实，需要开发人员手动调用 <code>remove</code> 方法回收不再使用的 <code>ThreadLocal</code>。</p>
<h3 id="脏数据">脏数据<a hidden class="anchor" aria-hidden="true" href="#脏数据">#</a></h3>
<p>在线程池环境下，由于线程的复用，<code>ThreadLocal</code> 的脏数据问题比较常见。</p>
<p>设想如下场景：用户 A 登录了网站，请求执行某些任务，为了后续方便，系统将部分用户信息保存到 <code>ThreadLocal</code> 中，但是忘记在任务完成后将这些信息手动清理；随后用户 B 也登录了同一个系统，执行了相同的任务，因为线程池中线程的复用，他居然获得到了用户 A 的某些信息，这显然是不行的。</p>
<p>上述场景用简单的代码模拟如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ThreadDirtyData</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">ExecutorService</span> <span class="n">executorService</span> <span class="o">=</span> <span class="n">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="n">1</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">executorService</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="k">new</span> <span class="n">UserTask</span><span class="o">(</span><span class="s">&#34;userA&#34;</span><span class="o">));</span>                   
</span></span><span class="line"><span class="cl">        <span class="n">executorService</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="k">new</span> <span class="n">UserTask</span><span class="o">(</span><span class="s">&#34;userB&#34;</span><span class="o">));</span>                   
</span></span><span class="line"><span class="cl">                                                                  
</span></span><span class="line"><span class="cl">        <span class="n">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="n">3</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">executorService</span><span class="o">.</span><span class="na">shutdownNow</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">UserData</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">String</span> <span class="n">data</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="nf">UserData</span><span class="o">(</span><span class="n">String</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">this</span><span class="o">.</span><span class="na">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@Override</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">UserContext</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">ThreadLocal</span><span class="o">&lt;</span><span class="n">UserData</span><span class="o">&gt;</span> <span class="n">CONTEXT</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ThreadLocal</span><span class="o">&lt;&gt;();</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">set</span><span class="o">(</span><span class="n">UserData</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">CONTEXT</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">data</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="n">UserData</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">CONTEXT</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">remove</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">CONTEXT</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">class</span> <span class="nc">UserTask</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">String</span> <span class="n">userName</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="nf">UserTask</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">userName</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@Override</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">UserData</span> <span class="n">userData</span> <span class="o">=</span> <span class="n">UserContext</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="n">userData</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">UserContext</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="k">new</span> <span class="n">UserData</span><span class="o">(</span><span class="n">userName</span> <span class="o">+</span> <span class="s">&#34;&#39;s data&#34;</span><span class="o">));</span>
</span></span><span class="line"><span class="cl">            <span class="n">userData</span> <span class="o">=</span> <span class="n">UserContext</span><span class="o">.</span><span class="na">get</span><span class="o">();</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">userData</span><span class="o">);</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>执行结果：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">userA</span><span class="err">&#39;</span><span class="n">s</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl"><span class="n">userA</span><span class="err">&#39;</span><span class="n">s</span> <span class="n">data</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>即使执行我们执行的任务是用户 B 的 Task，但是还是获取到了 A 的数据。</p>
<p>解决方案与内存泄漏相同，<code>ThreadLocal</code> 使用完，手动调用 <code>remove</code> 进行清理。</p>
<h3 id="threadlocal-数据向子线程传递">ThreadLocal 数据向子线程传递<a hidden class="anchor" aria-hidden="true" href="#threadlocal-数据向子线程传递">#</a></h3>
<p><code>ThreadLocal</code> 数据对于它的子线程是不可见的，但很多场景下需要在子线程中使用父线程的数据，<code>InheritableThreadLocal</code> 由此而生。</p>
<p>在 <code>Thread</code> 的 <code>init</code> 方法中有这么一段：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span> <span class="o">(</span><span class="n">inheritThreadLocals</span> <span class="o">&amp;&amp;</span> <span class="n">parent</span><span class="o">.</span><span class="na">inheritableThreadLocals</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span>     
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="o">.</span><span class="na">inheritableThreadLocals</span> <span class="o">=</span>                                     
</span></span><span class="line"><span class="cl">        <span class="n">ThreadLocal</span><span class="o">.</span><span class="na">createInheritedMap</span><span class="o">(</span><span class="n">parent</span><span class="o">.</span><span class="na">inheritableThreadLocals</span><span class="o">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>而在父线程创建子线程时，会调用到这里的方法，从而将父线程 <code>inheritableThreadLocals</code> 中的所有元素拷贝给子线程的 <code>inheritableThreadLocals</code>。<code>createInheritedMap</code> 里面的内容比较简单，这里就不再深入了，感兴趣的可以自己去看看。</p>
<p>但是在线程池的环境下，由于线程都已经自己创建好了，当任务从上游的父线程提交给线程池中的线程执行时，没有调用到上面的这个 <code>init</code> 过程，自然就没法向线程池中的线程传递数据了。针对这个问题，阿里提供了一个开源的 <code>TransmittableThreadLocal</code>，详细使用和原理这里就不展开了，有需要的可以自行查阅 <a href="https://github.com/alibaba/transmittable-thread-local">官网</a>。</p>


  </div>

  <footer class="post-footer">
    <ul class="post-tags">
      <li><a href="https://www.liyangjie.cn/tags/java/">Java</a></li>
      <li><a href="https://www.liyangjie.cn/tags/threadlocal/">ThreadLocal</a></li>
      <li><a href="https://www.liyangjie.cn/tags/reference/">Reference</a></li>
    </ul><div id="post-licensing" class="admonition warning">
    <div class="details-summary admonition-title">
        版权声明 <a rel="license" target="_blank" href="https://creativecommons.org/licenses/by/4.0/deed.zh"><i class="fab fa-creative-commons"></i> <i class="fab fa-creative-commons-by"></i></a>
    </div>
    
    <div class="details-content">
        <div class="admonition-content">
            <ul>
                <li>本文作者：SadBird。</li>
                <li>本文链接：https://www.liyangjie.cn/posts/work/threadlocal-source/。</li>
                <li>许可说明：本站所有文章除特殊声明外，均使用 <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="license" target="_blank"><i class="fab fa-creative-commons"></i> <i class="fab fa-creative-commons-by"></i> CC BY 4.0 </a> 许可协议，转载请注明出处。</li>
            </ul>
        </div>
    </div>
</div>
<nav class="paginav">
  <a class="prev" href="https://www.liyangjie.cn/posts/hobby/hugo-git-actions/">
    <span class="title"><i class="fas fa-angle-double-left"></i> Prev Page</span>
    <br>
    <span>Hugo 部署——Github Actions</span>
  </a>
  <a class="next" href="https://www.liyangjie.cn/posts/work/threadlocal-reference/">
    <span class="title">Next Page <i class="fas fa-angle-double-right"></i></span>
    <br>
    <span>ThreadLocal 分析（上）——Java 中的引用</span>
  </a>
</nav>

  </footer>
<footer class="tc-container" id="comment">
    <div class="tc-title"><p class="c-title">Discussion</p></div>
    <div id="tcomments"></div>
</footer>
<script crossorigin="anonymous" src="/js/twikoo.min.64322d6748f9b8b12dfb029616065f8eeed16467444adde58bab7d98c5733adf.js" integrity="sha256-ZDItZ0j5uLEt&#43;wKWFgZfju7RZGdESt3li6t9mMVzOt8="></script>
<script>
    twikoo.init({
        envId: 'https://twikoo-nu-red.vercel.app/',
        el: '#tcomments',
        region: 'ap-shenzhen', 
        
        lang: 'zh-CN', 
    });
</script>
</article>
    </main>
    
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
        <path d="M12 6H0l6-6z" />
    </svg>
</a><script>
    class Accordion {
        constructor(el) {
            
            this.el = el;
            
            this.summary = el.querySelector('summary');
            
            this.content = el.querySelector('.inner');

            
            this.animation = null;
            
            this.isClosing = false;
            
            this.isExpanding = false;
            
            this.summary.addEventListener('click', (e) => this.onClick(e));
        }

        onClick(e) {
            
            e.preventDefault();
            
            this.el.style.overflow = 'hidden';
            
            if (this.isClosing || !this.el.open) {
            this.open();
            
            } else if (this.isExpanding || this.el.open) {
            this.shrink();
            }
        }

        shrink() {
            
            this.isClosing = true;
            
            
            const startHeight = `${this.el.offsetHeight}px`;
            
            const endHeight = `${this.summary.offsetHeight}px`;
            
            
            if (this.animation) {
            
            this.animation.cancel();
            }
            
            
            this.animation = this.el.animate({
            
            height: [startHeight, endHeight]
            }, {
            duration: 400,
            easing: 'ease-out'
            });
            
            
            this.animation.onfinish = () => this.onAnimationFinish(false);
            
            this.animation.oncancel = () => this.isClosing = false;
        }

        open() {
            
            this.el.style.height = `${this.el.offsetHeight}px`;
            
            this.el.open = true;
            
            window.requestAnimationFrame(() => this.expand());
        }

        expand() {
            
            this.isExpanding = true;
            
            const startHeight = `${this.el.offsetHeight}px`;
            
            const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;
            
            
            if (this.animation) {
            
            this.animation.cancel();
            }
            
            
            this.animation = this.el.animate({
            
            height: [startHeight, endHeight]
            }, {
            duration: 400,
            easing: 'ease-out'
            });
            
            this.animation.onfinish = () => this.onAnimationFinish(true);
            
            this.animation.oncancel = () => this.isExpanding = false;
        }

        onAnimationFinish(open) {
            
            this.el.open = open;
            
            this.animation = null;
            
            this.isClosing = false;
            this.isExpanding = false;
            
            this.el.style.height = this.el.style.overflow = '';
        }
    }

    document.querySelectorAll('.toc details').forEach((el) => {
        new Accordion(el);
    });
</script>
<a href="#comment" aria-label="go to bottom comment" title="Go to Bottom Comment (Alt + B)" class="top-link bottom-comment" id="bottom-comment" accesskey="b" style="visibility: visible; opacity: 1;">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29.338 29.338" fill="currentcolor">
        <path d="M27.184,1.605H2.156C0.967,1.605,0,2.572,0,3.76v17.572c0,1.188,0.967,2.155,2.156,2.155h13.543
		l5.057,3.777c0.414,0.31,0.842,0.468,1.268,0.468c0.789,0,1.639-0.602,1.637-1.923v-2.322h3.523c1.188,0,2.154-0.967,2.154-2.155
		V3.76C29.338,2.572,28.371,1.605,27.184,1.605z M27.34,21.332c0,0.085-0.068,0.155-0.154,0.155h-5.523v3.955l-5.297-3.956H2.156
		c-0.086,0-0.154-0.07-0.154-0.155V3.759c0-0.085,0.068-0.155,0.154-0.155v0.001h25.029c0.086,0,0.154,0.07,0.154,0.155
		L27.34,21.332L27.34,21.332z M5.505,10.792h4.334v4.333H5.505C5.505,15.125,5.505,10.792,5.505,10.792z M12.505,10.792h4.334v4.333
		h-4.334V10.792z M19.505,10.792h4.334v4.333h-4.334V10.792z"/>
    </svg>
</a>

<script>
    let comment = document.getElementById("comment");
    let bottomToComment = document.getElementById("bottom-comment")
    
    document.addEventListener('scroll',function () {
        const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
        const offsetTop = comment.offsetTop
        const scrollTop = document.documentElement.scrollTop
        const top = offsetTop - scrollTop
        if (top <= viewPortHeight + 100) {
            bottomToComment.style.visibility = "hidden";
            bottomToComment.style.opacity = "0";
        } else {
            bottomToComment.style.visibility = "visible";
            bottomToComment.style.opacity = "1";
        }
    })
</script><footer class="footer">
    <span>&copy; 2022 <a href="https://www.liyangjie.cn/">染竹君的个人博客</a></span>
    <span>
        Powered by
        <a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
        <a href="https://git.io/hugopapermod" rel="noopener" target="_blank">PaperMod</a>
    </span>
    
</footer>

<script crossorigin="anonymous" src="/js/intersection-observer.min.c0ff09623e9f209a66c6130004e0422e1385fd8e8f8baaa14246a49c19827c68.js" integrity="sha256-wP8JYj6fIJpmxhMABOBCLhOF/Y6Pi6qhQkaknBmCfGg="></script>
<script crossorigin="anonymous" src="/js/lazyload.min.min.53e82bf9e8c145b953635b17fa3911ab6f3a8f1e4d37969aeb39d829d30fa6e9.js" integrity="sha256-U&#43;gr&#43;ejBRblTY1sX&#43;jkRq286jx5NN5aa6znYKdMPpuk="></script>


<script crossorigin="anonymous" src="/js/lightgallery.min.5d1410b8c831852c6c62b2dfad75f4e0f046a13e1826f97cd7a742523fab608c.js" integrity="sha256-XRQQuMgxhSxsYrLfrXX04PBGoT4YJvl816dCUj&#43;rYIw="></script>
<script crossorigin="anonymous" src="/js/lg-thumbnail.min.afa3995936244c14f68b85b13d657c368dc787cbcea863179950ac494dab6117.js" integrity="sha256-r6OZWTYkTBT2i4WxPWV8No3Hh8vOqGMXmVCsSU2rYRc="></script>
<script crossorigin="anonymous" src="/js/lg-zoom.min.a47b38d6f7138dce5f712f77780f339b5ec26af3bcd44950bcf736754ec88ed4.js" integrity="sha256-pHs41vcTjc5fcS93eA8zm17CavO81ElQvPc2dU7IjtQ="></script>
<script>
    const lazyLoadInstance = new LazyLoad({
        
    }); 
    
    const config = {
        selector: ".lightgallery",
        mode: "lg-slide",
        plugins: [lgZoom, lgThumbnail],
        speed: 400,
        hideBarsDelay: 2000,
        mousewheel: true,
        thumbnail: true,
        exThumbImage: "data-thumbnail",
        thumbWidth: 80,
        thumbContHeight: 80,
        mobileSettings: { controls: true, showCloseIcon: true, download: true, },
        licenseKey: "28AC9E09-3D8C45D8-8D6124E0-8FF74FF3", 
    };

    lightGallery(document.getElementsByClassName('post-content')[0], config);
</script>
<script>
    let details = document.getElementsByClassName('details')
    details = details || [];
    for (let i = 0; i < details.length; i++) {
        let element = details[i]
        const summary = element.getElementsByClassName('details-summary')[0];
        if (summary) {
            summary.addEventListener('click', () => {
                element.classList.toggle('open');
            }, false);
        }
    }
</script>

<script>
    let menu = document.getElementById('menu')
    if (menu) {
        menu.scrollLeft = localStorage.getItem("menu-scroll-position");
        menu.onscroll = function () {
            localStorage.setItem("menu-scroll-position", menu.scrollLeft);
        }
    }

    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener("click", function (e) {
            e.preventDefault();
            var id = this.getAttribute("href").substr(1);
            if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
                    behavior: "smooth"
                });
            } else {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
            }
            if (id === "top") {
                history.replaceState(null, null, " ");
            } else {
                history.pushState(null, null, `#${id}`);
            }
        });
    });

</script>
<script>
    var mybutton = document.getElementById("top-link");
    window.onscroll = function () {
        if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
            mybutton.style.visibility = "visible";
            mybutton.style.opacity = "1";
        } else {
            mybutton.style.visibility = "hidden";
            mybutton.style.opacity = "0";
        }
    };

</script>
<script>
    document.getElementById("theme-toggle").addEventListener("click", () => {
        if (document.body.className.includes("dark")) {
            document.body.classList.remove('dark');
            localStorage.setItem("pref-theme", 'light');
        } else {
            document.body.classList.add('dark');
            localStorage.setItem("pref-theme", 'dark');
        }
    })

</script>
<script>
    document.querySelectorAll('pre > code').forEach((codeblock) => {
        const container = codeblock.parentNode.parentNode;

        const copybutton = document.createElement('button');
        copybutton.classList.add('copy-code');
        copybutton.innerHTML = 'copy';

        function copyingDone() {
            copybutton.innerHTML = 'copied!';
            setTimeout(() => {
                copybutton.innerHTML = 'copy';
            }, 2000);
        }

        copybutton.addEventListener('click', (cb) => {
            if ('clipboard' in navigator) {
                navigator.clipboard.writeText(codeblock.textContent);
                copyingDone();
                return;
            }

            const range = document.createRange();
            range.selectNodeContents(codeblock);
            const selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            try {
                document.execCommand('copy');
                copyingDone();
            } catch (e) { };
            selection.removeRange(range);
        });

        if (container.classList.contains("highlight")) {
            container.appendChild(copybutton);
        } else if (container.parentNode.firstChild == container) {
            
        } else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
            
            codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
        } else {
            
            codeblock.parentNode.appendChild(copybutton);
        }
    });
</script>


<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [['$','$'], ['\\(','\\)']],
        processEscapes: true
      }
    });
</script>

<script type="text/javascript"
  src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script></body>

</html>